Data Validation in Laravel: The Right Way – Custom Validators

Amit Gupta
This entry is part 2 of 2 in the series Data Validation in Laravel - The Right Way

Data Validation in Laravel - The Right Way

In the previous part we learned how to validate data in Laravel using its in-built data validation package and how to abstract out our data validation to entity specific services to make the code reusable on the principles of DRY. Now, we can easily create a validation service for each entity in our app with its own respective validation rules, inject it wherever we want to validate data and easily fetch and display errors etc.

But what if we want more?

The source code for this tutorial is available here. You just need to run composer install to install the Laravel framework inside the project directory before you are able to run this code.

The Need for More

Out of the box, Laravel provides many useful and generic validation rules. But what if we want more? What if we need something more specific? In our example here, in TestFormValidator we have used alpha_dash to validate Names but that is not ideal to validate a full name. Generally, a person’s full name would consist of a First Name and a Last Name and maybe a Middle Name as well. All these would be separated by a space. Similarly if we want to validate the Pin Code in our form we cannot use alpha_num rule provided by Laravel, we should be able to allow spaces in it as well.

Well, Laravel provides the option to easily extend its validation package and add custom validation rules to it. In RocketCandy/Services/Validation/ create ValidatorExtended.php and add the following code to it:

<?php

namespace RocketCandy\Services\Validation;

use Illuminate\Validation\Validator as IlluminateValidator;

class ValidatorExtended extends IlluminateValidator {

	private $_custom_messages = array(
		"alpha_dash_spaces" => "The :attribute may only contain letters, spaces, and dashes.",
		"alpha_num_spaces" => "The :attribute may only contain letters, numbers, and spaces.",
	);

	public function __construct( $translator, $data, $rules, $messages = array(), $customAttributes = array() ) {
		parent::__construct( $translator, $data, $rules, $messages, $customAttributes );

		$this->_set_custom_stuff();
	}

	/**
	 * Setup any customizations etc
	 *
	 * @return void
	 */
	protected function _set_custom_stuff() {
		//setup our custom error messages
		$this->setCustomMessages( $this->_custom_messages );
	}

	/**
	 * Allow only alphabets, spaces and dashes (hyphens and underscores)
	 *
	 * @param string $attribute
	 * @param mixed $value
	 * @return bool
	 */
	protected function validateAlphaDashSpaces( $attribute, $value ) {
		return (bool) preg_match( "/^[A-Za-z\s-_]+$/", $value );
	}

	/**
	 * Allow only alphabets, numbers, and spaces
	 *
	 * @param string $attribute
	 * @param mixed $value
	 * @return bool
	 */
	protected function validateAlphaNumSpaces( $attribute, $value ) {
		return (bool) preg_match( "/^[A-Za-z0-9\s]+$/", $value );
	}

}	//end of class


//EOF

Here our class extends Laravel’s Illuminate\Validation\Validator class and adds two new methods (validateAlphaDashSpaces() and validateAlphaNumSpaces()) to validate data and their respective error messages with the placeholder :attribute in them which Laravel would replace at runtime with the name of whichever data field that is being validated.

Now the thing to note here is how we name the methods. All validation rule method names must have the validate prefix and the rest of it must be in Title Case (without spaces, of course). The validation rule will be in lowercase of what the method is named (without validation prefix) and each word will be separated by an underscore. So if we want to add an alpha_dash_spaces validation rule then our corresponding method will be named validateAlphaDashSpaces().

So we have added alpha_dash_spaces and alpha_num_spaces validation rules here. alpha_dash_spaces will allow letters, dashes (hyphens and underscores) and spaces while alpha_num_spaces will allow only letters, numbers (numbers 0-9) and spaces.

We are not done with this just yet, this class only extends Laravel’s validation class. We still have to make Laravel recognize it so that when we add the new rules to our vaidation service above, Laravel would know how to run the validation as per those rules.

Laravel docs state that we can do this:

Validator::resolver( function( $translator, $data, $rules, $messages ) {
	return new \RocketCandy\Services\Validation\ValidatorExtended( $translator, $data, $rules, $messages );
} );

and stick it in app/start/global.php or maybe create a new file inside app directory and load that file in app/start/global.php. But that does not look so clean, modifying files we don’t need to modify, sticking bits and pieces here and there. No, we would rather keep all this validation related code together, so we are going to create a Service Provider and glue our custom validation rules into Laravel’s validation package there.

Create ValidationExtensionServiceProvider.php inside RocketCandy/Services/Validation/ and add the following code to it:

<?php

namespace RocketCandy\Services\Validation;

use Illuminate\Support\ServiceProvider;

class ValidationExtensionServiceProvider extends ServiceProvider {

	public function register() {}

	public function boot() {
		$this->app->validator->resolver( function( $translator, $data, $rules, $messages = array(), $customAttributes = array() ) {
			return new ValidatorExtended( $translator, $data, $rules, $messages, $customAttributes );
		} );
	}

}	//end of class


//EOF

If you have created a Service Provider in Laravel before you would usually have used register() method to do whatever binding you needed. It is the only abstract method in the abstract class Illuminate\Support\ServiceProvider that we have extended here. The reason we cannot glue our validation extension in register() is that it is fired as soon as the Service Provider is loaded by Laravel and we would run into a volley of exceptions thrown at us as Laravel initializes its validation package later, so we would be trying to extend stuff that is not there using an object which does not exist. The boot() method on the other hand is fired just before a request is routed, so we can safely glue our stuff to Laravel’s validation package there.

Now we just need to tell Laravel to load this Service Provider and we would be all set. Open up your app/config/app.php and in the providers array add the following at the end:

'RocketCandy\Services\Validation\ValidationExtensionServiceProvider',

Now, open app/RocketCandy/Services/Validation/TestFormValidator.php and update the $rules property so it would look like this:

	public $rules = array(
		'name' => array( 'required', 'alpha_dash_spaces', 'max:200' ),
		'email' => array( 'required', 'email', 'min:6', 'max:200' ),
		'phone' => array( 'required', 'numeric', 'digits_between:8,25' ),
		'pin_code' => array( 'required', 'alpha_num_spaces', 'max:25' ),
	);

We replaced the alpha_dash validation rule for name with alpha_dash_spaces and alpha_num for pin_code with alpha_num_spaces.

Now if we navigate to http://<your-project-domain>/dummy/create we can enter spaces in the Name and Pin Code fields without any issues and the data will pass validation on submit.

Summary

So in this two part tutorial we learned to:

  1. Validate data in Laravel using its built in data validation package.
  2. Use the Object Oriented approach to abstract out data validation to it’s own separate service (Single Responsibility achievement unlocked).
  3. Create our own custom exceptions to use with our data validation service (instead of using TRUE/FALSE boolean values) and how to store and retrieve errors from them.
  4. Inject our data validation service in our Controller and use it.
  5. Extend Laravel’s validation package with our custom validation rules and auto-load it using a Service Provider.

Footnotes

For the sake of keeping this tutorial to the point I injected the validation service in our Controller and used it there. In a real-life project you would most likely use the validation service somewhere else where you would handle data sanitization and storage. Ideally, Controllers should be fat free and have the bare minimum of code.


Got thoughts? Questions? Fire away in the comments.

Data Validation in Laravel - The Right Way

<< Data Validation in Laravel: The Right Way

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.