Data Validation in Laravel: The Right Way – Custom Validators

Amit Gupta
Amit Gupta
Share

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.

Frequently Asked Questions about Laravel Data Validation and Custom Validators

What are the benefits of using Laravel for data validation?

Laravel is a popular PHP framework that offers a robust and flexible system for data validation. It provides a variety of validation rules that can be easily applied to your data, ensuring that it meets the required standards before it is processed. Laravel’s validation system also allows for custom validation rules, giving you the ability to define your own criteria for data validation. This can be particularly useful when you need to validate data in a way that is not covered by Laravel’s built-in validation rules.

How do I create custom validation rules in Laravel?

Laravel allows you to create custom validation rules using the Validator::extend method. This method takes two parameters: the name of the validation rule and a closure that receives three arguments – the attribute name, the attribute value, and a failure callback. Inside the closure, you can define the logic for your custom validation rule. If the validation fails, you should call the failure callback with an appropriate error message.

Can I use Laravel’s built-in validation rules with my custom rules?

Yes, Laravel allows you to use its built-in validation rules alongside your custom rules. This can be done by chaining the validation rules together in your validation logic. For example, you could use the ‘required’ rule to ensure that a field is not empty, and then use a custom rule to validate the format of the data.

How do I display validation error messages in Laravel?

Laravel makes it easy to display validation error messages in your views. When validation fails, Laravel redirects the user back to their previous location with all of the validation errors stored in the session. You can then display these errors in your views using the $errors variable, which is automatically made available to all views by Laravel.

Can I customize the error messages for my custom validation rules?

Yes, Laravel allows you to customize the error messages for your custom validation rules. This can be done by defining your custom messages in a language file, and then referencing these messages in your validation logic. This gives you the flexibility to tailor your error messages to your application’s specific needs.

How do I validate arrays in Laravel?

Laravel provides a convenient way to validate arrays using “dot notation”. You can specify the array field to be validated and then apply the validation rules to it. For example, if you have an array of emails, you can validate each email in the array using the ’email’ validation rule.

Can I use conditional validation rules in Laravel?

Yes, Laravel allows you to use conditional validation rules. This means that you can apply certain validation rules only if other conditions are met. This can be done using the sometimes method on the validator instance.

How do I validate file uploads in Laravel?

Laravel provides several validation rules for file uploads, such as ‘file’, ‘image’, ‘mimes’, and ‘size’. These rules allow you to validate the type and size of the uploaded file, ensuring that it meets your application’s requirements.

Can I use custom validation rules in form requests?

Yes, you can use custom validation rules in form requests. This can be done by defining the rules in the rules method of the form request class. You can then use these rules when validating the form request data.

How do I handle validation failures in Laravel?

When validation fails in Laravel, it automatically redirects the user back to their previous location with all of the validation errors stored in the session. You can then handle these errors in your views, displaying them to the user in a way that is appropriate for your application.