Data Validation in Laravel: The Right Way – Custom Validators
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:
- Validate data in Laravel using its built in data validation package.
- Use the Object Oriented approach to abstract out data validation to it’s own separate service (Single Responsibility achievement unlocked).
- 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. - Inject our data validation service in our Controller and use it.
- 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.