Data Validation in Laravel: The Right Way

Amit Gupta
Amit Gupta
Share

If an app was a world then data would be its currency. Every app, no matter what its purpose, deals in data. And almost every type of app works with user input, which means it expects some data from users and acts on it accordingly. But that data needs to be validated to make sure it is of correct type and a user (with nefarious intent) is not trying to break or crack into your app. Which, if you are making an application which requires user input, is why you would need to write code to validate that data as well before you do anything with it.

Once Upon a Time

At some point in time, you probably did your data validation like this:

<?php
$errors = array();

if ( empty( $_POST['name'] ) || ! is_string( $_POST['name'] ) ) {
    $errors[] = 'Name is required';
} elseif ( ! preg_match( "/^[A-Za-z\s-_]+$/", $_POST['name'] ) ) {
    $errors[] = 'Name can have only alphabets, spaces and dashes';
}

if ( empty( $_POST['email'] ) || ! is_string( $_POST['email'] ) ) {
    $errors[] = 'Email is required';
}

//...........
//.... some more code here
//...........

//display errors
if ( ! empty( $errors ) ) {
    for ( $i = 0; $i < count( $errors ); $i++ ) {
        echo '<div class="error">' . $errors[ $i ] . '</div>';
    }
}

Well, that was the stone age for you. Luckily, we have much better and more sophisticated validation packages these days (and have had them for quite some time in PHP).

If you use any application framework as the foundation of your app, chances are it will have its own or a recommended 3rd party data validation package, especially if it is a full stack framework. Since Laravel is a full stack framework, it comes with its own validation package. As always, you’re not constrained to use that and can use any other data validation package that you want. However, for the purpose of this tutorial, we will stick with what comes with Laravel by default.

Data Validation: The Laravel Way

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.

Now continuing the previous example, let’s assume we have a form which has a Name and an Email field and we would like to validate that data before saving it. Here’s how the previous rudimentary attempt at validation would translate with Laravel.

<?php
$validation = Validator::make(
    array(
        'name' => Input::get( 'name' ),
        'email' => Input::get( 'email' ),
    ),
    array(
        'name' => array( 'required', 'alpha_dash' ),
        'email' => array( 'required', 'email' ),
    )
);

if ( $validation->fails() ) {
    $errors = $validation->messages();
}

//...........
//.... some more code here
//...........

//display errors
if ( ! empty( $errors ) ) {
    foreach ( $errors->all() as $error ) {
        echo '<div class="error">' . $error . '</div>';
    }
}

In the code above we Laravel-ified the data validation by making use of the Validator facade. We passed an array of the data that we want to validate and an array of validation rules according to which we want the data validated. We get an object returned to us and then we check if our data validation failed or not. If it failed then we grab the error messages object (an object of Laravel’s MessageBag class) and then looped over it to print out all the error messages. The validation rules we used are built into the validation package and there are many available, some would even check into the database.

Now, it is not uncommon to come across code where the data validation has been placed at odd places, like in Controller methods or in data Models. Data validation code does not belong in either of those places, as such placement defies the concepts of Single Responsibility and DRY (Don’t Repeat Yourself).

Single Responsibility: One class should have one and only one job to perform. A Controller’s job is to act as a glue between the business logic and the client. It should grab the request and pass it on to someone who can process the request, it should not start processing the request itself.

Similarly, a Model’s job is to act as a broker between data source and rest of the application. It should only accept data for saving and give it when asked.

There is more than one school of thought on this; some would add data validation to Models but even those would not put actual data validation code inside a Model class. It would most likely be outsourced to another class which would accept data and tell whether the data is valid or not.

So where do we put the code which does data validation and which can be used anywhere in the application?

Validation as a Service

The ideal choice is to move out the validation code into separate class(es) which can be used as needed.

Continuing with our previous code example, let’s move it into its own class. Create a directory named RocketCandy inside app directory. This is our main directory (or domain directory) in which we will put all our custom stuff (services, exceptions, utility libraries, etc). Now create Services/Validation directory structure inside RocketCandy. Inside Validation directory, create Validator.php.

Now before we can proceed further, open up your composer.json and after the classmap in autoload node add RocketCandy namespace for PSR-4 autoloading. It would look something like this:

"autoload": {
		"classmap": [
			"app/commands",
			"app/controllers",
			"app/models",
			"app/database/migrations",
			"app/database/seeds",
			"app/tests/TestCase.php"
		],
		"psr-4": {
			"RocketCandy\\": "app/RocketCandy"
		}
	},

Then in your terminal, run composer dump-autoload -o so that composer can generate the autoloader for our RocketCandy namespace.

Now open up RocketCandy/Services/Validation/Validator.php. After we move the validation code from above, it will look something like this:

<?php

namespace RocketCandy\Services\Validation;

class Validator {

    public function validate() {
        $validation = \Validator::make(
            array(
                'name' => \Input::get( 'name' ),
                'email' => \Input::get( 'email' ),
           ),
            array(
                'name' => array( 'required', 'alpha_dash' ),
                'email' => array( 'required', 'email' ),
            )
        );

        if ( $validation->fails() ) {
            return $validation->messages();
        }
        
        return true;
    }

}   //end of class

//EOF

Now we could use this as:

<?php

$validator = new \RocketCandy\Services\Validation\Validator;
$validation = $validator->validate();

if ( $validation !== true ) {
    //show errors
}

This is somewhat better than what we were doing earlier but it is still not ideal for the following reasons:

  1. Our Validation class still is not DRY enough. We would need to copy over all this validation code to another class to validate data of another entity.
  2. Why are we fetching input data inside the validation class? There is no reason to do it there because it would limit us as to which data we can validate. Here we would be able to validate this data only if it came from a form input.
  3. There is no way of overriding validation rules, they are set in stone.
  4. The mechanism by which we get to know whether the data validated or not is not clean. Sure, it serves the purpose but this can definitely be improved upon.
  5. We are using a pseudo static call to Laravel’s validation package. This can be improved upon as well.

Solution?

We abstract out the validation code a bit further and we make use of exceptions.

First, let’s make our own custom exceptions. Create the Exceptions directory under RocketCandy and create BaseException.php. Open it up and put the following code in it.

<?php

namespace RocketCandy\Exceptions;

use Exception;
use Illuminate\Support\MessageBag;

abstract class BaseException extends Exception {

	protected $_errors;

	public function __construct( $errors = null, $message = null, $code = 0, Exception $previous = null ) {
		$this->_set_errors( $errors );

		parent::__construct( $message, $code, $previous );
	}

	protected function _set_errors( $errors ) {
		if ( is_string( $errors ) ) {
			$errors = array(
				'error' => $errors,
			);
		}

		if ( is_array( $errors ) ) {
			$errors = new MessageBag( $errors );
		}

		$this->_errors = $errors;
	}

	public function get_errors() {
		return $this->_errors;
	}

}	//end of class


//EOF

Here we created an abstract class and all our custom exceptions would inherit this class. The first parameter for the constructor is what we are concerned with, so let’s look at that. We make use of Laravel’s MessageBag to store our errors (if they are not in it already) so that we would have a uniform way to loop through and display those errors irrespective of whether the exception was thrown by validation service or any other. The _set_errors() method thus checks if a single error message as a string was passed or an array of error messages was passed. Accordingly, it stores them in a MessageBag object (unless it is already inside in which case it would be stored as is). And we have a getter method get_errors() which just returns the contents of our class variable as is.

Now, in the same directory create ValidationException.php and its code will be:

<?php

namespace RocketCandy\Exceptions;

class ValidationException extends BaseException {
}	//end of class


//EOF

That’s it, we don’t need anything else in here, it will be an empty shell because all that we need done will be handled by BaseException.

Now, we proceed with re-tooling our Validator class. We need to abstract out the validation code, throw ValidationException on error(s) and allow overriding of validation rules. So it would look like this:

<?php

namespace RocketCandy\Services\Validation;

use Illuminate\Validation\Factory as IlluminateValidator;
use RocketCandy\Exceptions\ValidationException;

/**
 * Base Validation class. All entity specific validation classes inherit
 * this class and can override any function for respective specific needs
 */
abstract class Validator {

	/**
	 * @var Illuminate\Validation\Factory
	 */
	protected $_validator;

	public function __construct( IlluminateValidator $validator ) {
		$this->_validator = $validator;
	}

	public function validate( array $data, array $rules = array(), array $custom_errors = array() ) {
		if ( empty( $rules ) && ! empty( $this->rules ) && is_array( $this->rules ) ) {
			//no rules passed to function, use the default rules defined in sub-class
			$rules = $this->rules;
		}

		//use Laravel's Validator and validate the data
		$validation = $this->_validator->make( $data, $rules, $custom_errors );

		if ( $validation->fails() ) {
			//validation failed, throw an exception
			throw new ValidationException( $validation->messages() );
		}

		//all good and shiny
		return true;
	}

} //end of class

//EOF

Here in this abstract class we have:

  1. Abstracted out the validation code. It can be used as is for validating data of any entity.
  2. Removed data fetching from the class. Validation class does not need to know where the data is coming from. It accepts an array of data to validate as a parameter.
  3. Removed validation rules from this class. Each entity can have its own set of validation rules either defined in the class or they can be passed as array to validate(). If you want to define rules in the child classes and want to be sure they’re present, I wrote about emulating abstract properties in PHP sometime back.
  4. Improved the mechanism by which validation failure can be determined. If the data validation fails the validation service would throw ValidationException and we can get the errors from that instead of checking for returned data type or values etc. This also means that we can throw another exception if validation rules are not defined. It would be a different exception and we would know immediately that we messed up somewhere.
  5. Removed the usage of static call for data validation. In here we now inject Laravel’s validation class in our class constructor. If we resolve our validation service out of Laravel’s IoC container (which we would) then we would not have to worry about the dependency injection into constructor here.

Now we would create a validation class for our form which would extend this abstract class. In the same directory create TestFormValidator.php and add following code into it:

<?php

namespace RocketCandy\Services\Validation;

class TestFormValidator extends Validator {

	/**
	 * @var array Validation rules for the test form, they can contain in-built Laravel rules or our custom rules
	 */
	public $rules = array(
		'name' => array( 'required', 'alpha_dash', 'max:200' ),
		'email' => array( 'required', 'email', 'min:6', 'max:200' ),
		'phone' => array( 'required', 'numeric', 'digits_between:8,25' ),
		'pin_code' => array( 'required', 'alpha_num', 'max:25' ),
	);

}	//end of class


//EOF

This is the class that we will instantiate to validate our form data. We have set the validation rules in this class and so we would just need to call validate() method on its object and pass our form data to it.

Note: If you have not made the artisan tool in your project directory an executable then you would need to replace ./artisan from the artisan commands in this tutorial and replace with /path/to/php artisan.

It is recommended you make artisan an executable, it saves the needless hassle to prefix php on every command.

Let’s make a Controller and a form properly to take this for a spin. In your terminal, navigate to your project directory and run

./artisan controller:make DummyController --only=create,store

It would create app/controllers/DummyController.php with two methods – create() and store(). Open up app/routes.php and add the following route directive.

Route::resource( 'dummy', 'DummyController', array(
	'only' => array( 'create', 'store' ),
) );

This will set up our Controller to work in a RESTful way; /dummy/create/ would accept GET requests and /dummy/store/ would accept POST requests. We can remove the only directive from both route and artisan command and the Controller would accept PUT and DELETE requests too but for the current exercise we don’t need them.

Now we need to add the code to our Controller, so open up app/controllers/DummyController.php. It would be an empty shell with create() and store() methods. We need to make create() render a view, so make it like this:

/**
	 * Show the form for creating a new resource.
	 *
	 * @return Response
	 */
	public function create() {
		return View::make( 'dummy/create' );
	}

We now need the view which we are rendering here and which will render our form.

First let’s create a layout file where we can put the HTML boilerplate code. Create layouts directory inside app/views and create default.blade.php inside it. Note the .blade suffix of the view name here. It tells Laravel that this view uses Laravel’s Blade templating syntax and should be parsed as such. Open it up and add the following boilerplate code to it:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">

		<title>Advanced Data Validations Demo</title>

		<!-- Bootstrap core CSS -->
		<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

		<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
		<!--[if lt IE 9]>
			<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
			<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
		<![endif]-->
	</head>

	<body>
		<div class="row">
			<h1 class="col-md-6 col-md-offset-3">Advanced Data Validations Demo</h1>
		</div>
		<p>&nbsp;</p>

		<div class="container">
			@yield( "content" )
		</div><!-- /.container -->

		<!-- Bootstrap core JavaScript -->
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
		<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
	</body>
</html>

It is simple boilerplate code for an HTML page which uses Bootstrap. The only thing to notice here is the placeholder where we would inject our view code; @yield( "content" ) will tell Laravel’s Blade parser that our view’s content section code should be injected at this place.

Now create app/views/dummy/create.blade.php and add following code to it:

@extends( "layouts/default" )

@section( "content" )
	<div class="row">
		<h3 class="col-md-6 col-md-offset-2">Test Form</h3>
	</div>
	<p>&nbsp;</p>
	@if ( ! $errors->isEmpty() )
	<div class="row">
		@foreach ( $errors->all() as $error )
		<div class="col-md-6 col-md-offset-2 alert alert-danger">{{ $error }}</div>
		@endforeach
	</div>
	@elseif ( Session::has( 'message' ) )
	<div class="row">
		<div class="col-md-6 col-md-offset-2 alert alert-success">{{ Session::get( 'message' ) }}</div>
	</div>
	@else
		<p>&nbsp;</p>
	@endif

	<div class="row">
		<div class="col-md-offset-2 col-md-6">
			{{ Form::open( array(
				'route' => 'dummy.store',
				'method' => 'post',
				'id' => 'test-form',
			) ) }}
				<div class="form-group">
					{{ Form::label( 'name', 'Name:' ) }}
					{{ Form::text( 'name', '', array(
						'id' => 'name',
						'placeholder' => 'Enter Your Full Name',
						'class' => 'form-control',
						'maxlength' => 200,
					) ) }}
				</div>
				<div class="form-group">
					{{ Form::label( 'email', 'Email:' ) }}
					{{ Form::text( 'email', '', array(
						'id' => 'email',
						'placeholder' => 'Enter Your Email',
						'class' => 'form-control',
						'maxlength' => 200,
					) ) }}
				</div>
				<div class="form-group">
					{{ Form::label( 'phone', 'Phone:' ) }}
					{{ Form::text( 'phone', '', array(
						'id' => 'phone',
						'placeholder' => 'Enter Your Phone Number',
						'class' => 'form-control',
						'maxlength' => 25,
					) ) }}
				</div>
				<div class="form-group">
					{{ Form::label( 'pin_code', 'Pin Code:' ) }}
					{{ Form::text( 'pin_code', '', array(
						'id' => 'pin_code',
						'placeholder' => 'Enter Your Pin Code',
						'class' => 'form-control',
						'maxlength' => 25,
					) ) }}
				</div>
				<div class="form-group">
					{{ Form::submit( '&nbsp; Submit &nbsp;', array(
						'id' => 'btn-submit',
						'class' => 'btn btn-primary',
					) ) }}
				</div>
			{{ Form::close() }}
		</div>
	</div>
@stop

In this view we first tell Laravel that we want to use the default.blade.php layout using the directive @extends( "layouts/default" ). Then we create the content section as that is the one we have set to be injected in the layout. The view renders a form with Name, Email, Phone and Pin Code fields using Laravel’s form builder (we could use HTML5 fields here with the basic browser validation enabled, like for Email we could use Form::email(), but since we want to check our server side validation we are using normal text fields for input). Also above the form we check whether we have anything in the $errors var and to display it if there are any errors. Also we check for any flash message that we might have.

If we now navigate to http://<your-project-domain>/dummy/create (the assumption here is that you have already setup a domain on your development stack for this project) then we will have the form rendered for us. Now we need to be able to accept the data from this form and have the data validated. So back in our DummyController we would inject our TestFormValidator in the constructor and accept the data in store() and validate it. So the Controller would look like this now:

<?php

use RocketCandy\Exceptions\ValidationException;
use RocketCandy\Services\Validation\TestFormValidator;

class DummyController extends BaseController {

	/**
	 * @var RocketCandy\Services\Validation\TestFormValidator
	 */
	protected $_validator;

	public function __construct( TestFormValidator $validator ) {
		$this->_validator = $validator;
	}

	/**
	 * Show the form for creating a new resource.
	 *
	 * @return Response
	 */
	public function create() {
		return View::make( 'dummy/create' );
	}


	/**
	 * Store a newly created resource in storage.
	 *
	 * @return Response
	 */
	public function store() {
		$input = Input::all();

		try {
			$validate_data = $this->_validator->validate( $input );

			return Redirect::route( 'dummy.create' )->withMessage( 'Data passed validation checks' );
		} catch ( ValidationException $e ) {
			return Redirect::route( 'dummy.create' )->withInput()->withErrors( $e->get_errors() );
		}
	}


}	//end of class

//EOF

Laravel will take care of the dependency injection in the constructor as all Controllers are resolved out of its IoC container by default, so we don’t have to worry about that. In the store() method we grab all the form input in a var and inside try/catch we pass the data to our validation service. If the data validates then it will redirect us back to the form page with a success message else it will throw ValidationException which we will catch, grab the errors and return back to the form to display what went wrong.

Summary

In this first part, we learned how to do data validations the Laravel way and how to abstract out validation to a service which can be used to validate data for each entity anywhere in the app. In the next and final part we will learn how to extend Laravel’s validation package to have our own custom rules.


Got thoughts? Questions? Fire away in the comments.

Frequently Asked Questions on Data Validation in Laravel

How can I customize error messages in Laravel data validation?

Laravel allows you to customize the error messages that are displayed during data validation. You can do this by passing a third argument to the validate method. This argument should be an array where the keys are the attribute names and the values are the custom messages. For example:

$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
], [
'title.required' => 'A title is required',
'body.required' => 'Please provide the body text',
]);
In this example, if the title or body fields are not filled in, the custom messages will be displayed instead of the default ones.

How can I use data validation rules in Laravel?

Laravel provides a variety of data validation rules that you can use to ensure the data your application receives is in the correct format. These rules are defined in the rules method of your form request. For example:

public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
In this example, the title field must be unique in the posts table, not exceed 255 characters, and is required. The body field is also required.

How can I validate arrays in Laravel?

Laravel also provides a way to validate arrays. You can do this by using the * character in your validation rules. For example:

public function rules()
{
return [
'product.*.id' => 'required|exists:products,id',
'product.*.quantity' => 'required|integer|min:1',
];
}
In this example, each id in the product array must exist in the products table and each quantity must be an integer greater than 0.

How can I create custom validation rules in Laravel?

Laravel allows you to create your own validation rules. You can do this by creating a new rule object using the make:rule Artisan command. For example:

php artisan make:rule Uppercase
This command will create a new rule object in the app/Rules directory. You can then define the validation logic in the passes method and the error message in the message method.

How can I stop validation on the first failure in Laravel?

Laravel provides a bail rule that you can use to stop validation on the first failure. This means that if one of your validation rules fails, the rest will not be checked. For example:

public function rules()
{
return [
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
];
}
In this example, if the title field is not filled in, the unique and max rules will not be checked.

How can I validate a file upload in Laravel?

Laravel provides several validation rules for file uploads. For example, you can check if the uploaded file is an image, if it’s of a certain type, or if it doesn’t exceed a certain size. Here’s an example:

public function rules()
{
return [
'photo' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
];
}
In this example, the photo field is required, must be an image, must be one of the specified types, and must not exceed 2048 kilobytes.

How can I validate data before it reaches my controller in Laravel?

Laravel provides a way to validate data before it reaches your controller by using form requests. Form requests are custom request classes that contain validation logic. To create a form request, you can use the make:request Artisan command. For example:

php artisan make:request StorePostRequest
This command will create a new form request in the app/Http/Requests directory. You can then define your validation rules in the rules method.

How can I display validation error messages in Laravel?

Laravel automatically stores the error messages in a errors session variable if validation fails. You can display these messages in your views by looping over this variable. For example:

@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
In this example, if there are any validation errors, they will be displayed in an unordered list.

How can I validate optional fields in Laravel?

Laravel provides a sometimes rule that you can use to conditionally add validation rules. This is useful for fields that are not always required. For example:

public function rules()
{
return [
'title' => 'sometimes|required|unique:posts|max:255',
'body' => 'required',
];
}
In this example, the title field is only required if it is present in the input array.

How can I validate a checkbox in Laravel?

Laravel provides a boolean rule that you can use to validate checkboxes. This rule checks if the field is true or false. For example:

public function rules()
{
return [
'terms' => 'required|boolean',
];
}
In this example, the terms field must be either true or false.