Input Validation with Laravel
Almost every interactive web application needs to validate data. For instance, a registration form probably requires the password to be confirmed. Maybe the e-mail address must be unique. Validations allow you to ensure that only valid data is saved in the database.
In this lesson, we’ll describe the validation methods available in Laravel and how to use them effectively. We’ll also explore how those validation methods interact with your model’s attributes and how Laravel’s built-in error-messaging system can be used effectively in your application’s user interface to provide descriptive feedback.
Validations are used to ensure that the data is of a certain format, type, length, or value. Some plain English examples of data validation rules would be:
- Account “usernames” must be at least six alpha-numeric characters long and must be unique.
- “Marital status” must be one of our predefined values (i.e. “Married”, “Unmarried” etc.).
- “Postal codes” must contain only numeric digits.
Validating data can be a cumbersome process. Thankfully, Laravel has a library, aptly named “Validation” that will do all the hard work for us and make this mundane part of programming as simple and as easy as possible.
Why Use Validations?
Why do you need to validate data in the first place? Well, there are a lot times when it comes in handy:
- Data validations are a handy way to ensure that your data is always in a clean and expected format before it gets into your database. From a programmer’s point of view, if you know your data is cleaned and scrubbed, you won’t need to write as many checks and balances or deal with as many potential error-handling procedures. From a database administrator’s (DBA) point of view, it also means there is less need for database checks and procedures to ensure data integrity. And last but not least, from a user’s point of view, it usually means a more intuitive experience where typos and common errors are immediately caught and dealt with.
- Validations make your database more secure; potentially harmful data or SQL injection attacks can be prevented from getting into your database.
- Validations help save on band-width and processing; rejecting bad data as early as possible within the life cycle of your Laravel application saves on resources. There is no need to involve the expensive call to the database until the data is really ready to be stored.
- Often, business logic is applied to specific data values or types; validations make for more accurate results and expected execution in these situations.
Validation Approaches
There are several approaches to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations. All of these options are discussed below:
Validation | Pros | Cons |
---|---|---|
Native database constraints | You can use database constraints, triggers, and stored procedures to validate data. If your database is used by other applications, especially if they aren’t all using Laravel to do so, it may be a good idea to to move, or at least duplicate, your critical validations directly inside of the database. This way all applications are dealing with the same rules and can reasonably expect the data to be in the same format regardless of its originating source. Additionally, database-level validations can efficiently handle some things (such as uniqueness of a column in heavily-used tables) that can be difficult to implement otherwise. | Testing and maintenance is more difficult. The other big downside to this approach is that your validations would be database- and schema-specific, which makes migrations or switching to another database backend more difficult in the future. |
Client-side validations | If combined with other server-side techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. | Generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user’s browser. |
Controller-level validations | Fairly trivial to implement. | Putting too much code inside controllers makes them unwieldy and difficult to test and maintain. Whenever possible, it’s a good idea to keep your controllers skinny, as it will make your application a pleasure to work with in the long run. |
Model-level validations | This is the recommended way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Laravel makes model validations easy to use and provides several built-in helpers for common needs. You don’t need to know any special syntax or language such as T-SQL or PL/SQL; you just write Laravel methods with PHP code. | Model validations are only applied within your Laravel application. If other applications or programs, such as Java or Python programs, access your database, they would need to do their own data validations. |
Laravel Validation Walk-through
Validating incoming data to make sure data is in accordance to business logic rules is easy to do in Laravel.
Let’s walk through an example application. We will create a tiny Laravel application to allow users to sign-up with our website. Our application will take information about the user, validate it and then save it in a database.
Note: Source code for this demo application is available at the github repository
We’ll begin by creating a database named “laravel_validation”. We’ll be using the MySql database engine for this project. Here’s the relevant segment of my
app/config/database.php
configuration file:<?php
return array(
'default' => 'mysql',
'connections' => array(
'mysql' => array(
'driver' => 'mysql',
'host' => '127.0.0.1',
'database' => 'laravel_validation',
'username' => 'Your_Database_Username',
'password' => 'Your_Database_Password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
...
For this demo app, we only need one table to save user data. Let’s create our “users” table by leveraging the power of Laravel migrations:
$ php artisan migrate:install
Nice! Now we're ready to do some migrating!
$ php artisan migrate:make create_users_table
Migration created successfully!
Let’s open up the new migration file in the
app/database/migrations/
folder (mine is named2013_01_02_074734_create_users_table.php
) and paste the following schema code:<?php
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration {
public function up()
{
Schema::create('users', function($t) {
$t->increments('id');
$t->string('real_name', 80);
$t->string('email', 64);
$t->string('password', 64);
$t->integer('age')->nullable();
});
}
public function down()
{
Schema::drop('users');
}
}
The
users
table schema is not intended to be realistic, but merely to illustrate the use of validations.
Let’s run the migration to create the table in our database:
$ php artisan migrate
Migrated: 2013_01_02_074734_create_users_table
Now, let’s create the Laravel model class that relates to this table. We will create a new PHP file
User.php
in theapp/models/
folder.
Our
User
data model will contain the validation logic for newly registered user data. Let’s copy and paste the following code template:<?php
class User extends Eloquent {
protected $table = 'users';
public $timestamps = false;
public static function validate($input) {
$rules = array(
# place-holder for validation rules
);
# validation code
}
}
Before we can start the validation process we need to create a set of rules that will be used to validate each attribute of our model. In Laravel rules are defined in an array format. Let’s take a look:
$rules = array(
'real_name' => 'Required|Min:3|Max:80|Alpha',
'email' => 'Required|Between:3,64|Email|Unique:users',
'age' => 'Integer|Min:18',
'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation'=>'Required|AlphaNum|Between:4,8'
);
The array key is the field that is being validated upon, and the array value contains a chain of validation rules. In Laravel, we separate the validation rules with the pipe (|) symbol. Let’s taka a closer look:
'real_name' => 'Required|Min:3|Max:80|Alpha',
In this case, we are validating the
real_name
attribute of our User
data model. The Required
rule indicates that the input should not be a blank string. The length of the user’s real name must be a minimum of 3 characters (Min:3
) and a maximum length of 80 characters (Max:80
). The Alpha
rule will check to make sure that the real_name
field only contains alphabets (letters a-z and A-Z).'email' => 'Required|Between:3,64|Email|Unique:users',
Our
email
validation rules are similar to the previous one. The Between
rule validates that the length of the email
field is within the given range (this is an alternative to the Min
& Max
rules described above). The Email
rule will check to make sure that the attribute is a properly formatted e-mail address. The Unique
rule validates that an attribute is unique on a given database table. In the example above, the email
attribute will be checked for uniqueness on theusers
table.'age' => 'Integer|Min:18',
Our
age
field must contain an integer and the value must be at least 18 or greater. Notice the absence of the Required
rule because this is an optional field.'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation' =>'Required|AlphaNum|Between:4,8'
The
AlphaNum
rule ensures that the password
attribute consists of alphabets and numbers only. The Confirmed
rule ensures that, for a given attribute, a matching attribute_confirmation attribute exists. It is designed to simplify the process of confirming two HTML form field values - a common practice for things like password fields. Your table has one field associated with an attribute, and your HTML form has two fields that are similar except that one of them has “_confirmation” appended to its name. Given the example above, the Validator will make sure that the password
attribute matches the password_confirmation
attribute in the array being validated.
An example explains this best:
- In your HTML form, you need to have something like this:
<input type="password" name="password"> <input type="password" name="password_confirmation">
- Now, when the data is submitted to your model, the
confirmed
rule will check that the values in these two fields match as part of the validation process. The validation will succeed if both the fields match.
Don’t worry, we will cover all the validation rules later. For now, let’s see the validation in action:
$rules = array(
...
);
$v = Validator::make($input, $rules);
if( $v->passes() ) {
# code for validation success!
} else {
# code for validation failure
}
We create our validator object with the
Validator::make()
method, passing it our input array, and our rules array.if( $v->passes() ) {
# code for validation success!
} else {
# code for validation failure
}
As you can see, we use the
passes()
method of the Validator
object to check the result of the validation attempt. It returns true if no errors were found in the object (validation was successful), and false otherwise.
The opposite counter-part to the
passes()
method is the fails()
method which does just the opposite - it will return true if the validation fails. If you wish, you could rewrite the above snippet using the fails()
method:if( $v->fails() ) {
# code for validation failure
} else {
# code for validation success!
}
Validation Error Messages
After
Validator
has performed validations, any errors found can be accessed through the getMessages()
method, which returns a collection of errors. By definition, an object is valid if this collection is empty after running validations. When your validation fails, you will want to find out what went wrong. You may access the error messages via thegetMessages()
method to easily retrieve the failed validations.
The Laravel error collector has several simple functions for retrieving your messages:
if ($v->getMessages()->has('email'))
{
# The e-mail attribute has errors...
}
The
has()
method determines if an attribute has an error message.$messages = $v->getMessages()->first('email');
The
first()
method retrieves the first error message for an attribute. Sometimes you may need to format the error message by wrapping it in HTML tags. No problem. Along with the :message
place-holder, pass the format as the second parameter to the method.$messages = $v->getMessages()->first('email', '<p>:message</p>');
If you want to access all of the error messages for a given attribute, you can call the
get()
method:$messages = $v->getMessages()->get('email');
And if you want to get all of the error messages for all attributes, then you will use the
all()
method:$messages = $v->getMessages()->all();
Both these functions also support custom formatting:
$messages = $v->getMessages()->get('email', '<p>:message</p>');
$messages = $v->getMessages()->all('<p>:message</p>');
Going back to our
User
model example, let’s flesh out the validate()
method:public function validate($input) {
$rules = array(
'real_name' => 'Required|Min:3|Max:80|Alpha',
'email' => 'Required|Between:3,64|Email|Unique:users',
'age' => 'Integer|Min:18',
'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation'=>'Required|AlphaNum|Between:4,8'
);
return Validator::make($input, $rules);
}
Our
validate()
function simply returns the Validator
object. We can use this object to check if the validations have succeeded.
For our demo app, we will define two routes:
Route::get('/', function() { });
Route::post('signup', function() { });
The
GET /
route will display a very simple HTML sign-up form, while the POST /signup
will perform input validation and database-related tasks.
Let’s implement the
GET /
route:Route::get('/', function() {
return View::make('signup');
});
It simply returns a HTML page.
Here’s the corresponding view file
app/views/signup.blade.php
for the route:<html>
<body>
<h2>Sign-up Form</h2>
@if ( $errors->count() > 0 )
<p>The following errors have occurred:</p>
<ul>
@foreach( $errors->all() as $message )
<li>{{ $message }}</li>
@endforeach
</ul>
@endif
<form method="POST" action="/signup">
<!-- real name field -->
<p>
<label for="real_name">Real Name (*)</label><br/>
<input type="text" name="real_name" id="real_name">
</p>
<!-- email field -->
<p>
<label for="email">E-mail (*)</label><br/>
<input type="text" name="email" id="email">
</p>
<!-- password field -->
<p>
<label for="password">Password (*)</label><br/>
<input type="password" name="password" id="password">
</p>
<!-- password confirmation field -->
<p>
<label for="password_confirmation">Password (*)</label><br/>
<input type="password" name="password_confirmation" id="password_confirmation">
</p>
<!-- age field -->
<p>
<label for="age">Age</label><br/>
<input type="text" name="age" id="age">
</p>
<p><small>Fields denoted with an asterisk (*) are mandatory</small></p>
<!-- submit button -->
<p><input type="submit" value="Sign-up"></p>
</form>
</body>
</html>
We are using the
$errors->count()
method to check if we have any form validation errors. If we do have any errors, they will be enumerated in the final web page wrapped inside the <li>
HTML tag. Rest of the HTML form is fairly self-explanatory; all the HTML form fields match our User
data model.
This is what the form will look like initially:
.. after validation failure:
Now, let’s implement the
POST /signup
route:Route::post('signup', function() {
$v = User::validate(Input::all());
if ( $v->passes() ) {
User::create(array(
'real_name'=> Input::get('real_name'),
'email'=> Input::get('email'),
'password'=> Hash::make(Input::get('password')),
'age'=> Input::has('age') ? intval(Input::get('age')) : null,
));
return 'Thanks for registering!';
} else {
return Redirect::to('/')->withErrors($v->getMessages());
}
});
Let’s go through the code quickly:
$v = User::validate(Input::all());
We grab the form submission data using the
Input::all()
method. We pass this input data to our User::validate()
method to create a new validator object.if ( $v->passes() ) {
User::create(array(
'real_name'=> Input::get('real_name'),
'email'=> Input::get('email'),
'password'=> Hash::make(Input::get('password')),
'age'=> Input::has('age') ? intval(Input::get('age')) : null,
));
return 'Thanks for registering!';
}
If the validation had passed, we save the user to the database and return the registration success message.
else {
return Redirect::to('/')->withErrors($v->getMessages());
}
If the validation fails, we redirect back to the sign-up form and flash the validation errors to the session so they will be available for us to display.
But, notice we did not explicitly bind the “errors” to the view in our
GET /
route. However, an errors variable ($errors
) will still be available in the view. Laravel intelligently determines if errors exist in the session, and if they do, binds them to the view for you. If no errors exist in the session, an empty message container will still be bound to the view. In your views, this allows you to always assume you have a message container available via the errors variable.Laravel Validation Rules
Below is a list of Laravel validation rules:
Rule | Description |
---|---|
Required | Ensure that a value for a field is present, and is not an empty string. |
Alpha | Validate that an attribute contains only alphabetic characters. |
AlphaNum | Validate that an attribute contains only alphabetic and numeric characters. |
AlphaDash | Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. |
Size:5 | [string] The string must be exactly 5 characters long. [numeric] The value must be 5. |
Between:5,10 | [string] The length of the string must be between 5 and 10 characters. [numeric] The value must be between 5 and 10. |
Min:5 | [string] The length of the string must be between 5 characters or more [numeric] The value must be equal to or greater than 5. [file] The file size must be 5 kilobytes or more. |
Max:5 | [string] The length of the string must be less than or equal to 5. [numeric] The value must be less than or equal to 5. [file] The file size must be 5 kilobytes or less. |
Numeric | The value must be numeric. |
Integer | The value must be an integer or whole number. |
In:small,medium,large | Ensure that the value is contained within the list of values provided. |
NotIn:php,ruby | Ensure that none of the values provided match the value. |
Confirmed | The value of the field must match a confirmation field, named in the format_confirmation . |
Accepted | The field value must be equal to yes or 1 . Useful for validating checkboxes. |
Same:password2 | The field value must match the field specified by the same rule. |
Different:old_password | The field value must not match the field specified by the same rule. |
Regex:/^([a-z])+$/i | The field value must match the provided regular expression. |
Unique:users | The validator will look at the “users” database table, and make sure that the value is unique within the column that has the same name as the field name. Useful for making sure that duplicate usernames or email addresses don’t occur. If you would like to specify an alternate column name, simply pass it as a second parameter:unique:users,email_address . You can also force the rule to ignore a provided id by passing it as a third parameter: unique:users,email_address,50 . If the primary key of your table has an alternate column name, you may pass it as the fourth parameter:unique:users,email_address,50,alternate_id_column_name . |
Exists:users,email,john@email.com | Acts as the opposite of Unique , the value must already exist in the given database table. Yu can pass a second parameter to refer to an alternate column name:exists:users,email_address |
ExistCount:users | Get the number of records that exist in database |
Before:2012-12-21 | Validate that a date attribute is before the given date. |
After:1986-05-28 | Validate that a date attribute is after the given date. |
Ip | The value must be a valid IP address. |
Email | The value must be a valid email address. |
Url | Validate that an attribute is a URL. |
ActiveUrl | Validate that an attribute is an active URL. Note: The ActiveUrl rule uses checkdnsr to verify the URL is active. |
Mimes:jpg,gif | The value must be a $_FILE which whose MIME type matches the file extensions provided. You can add additional MIME types to the array in config/mimes.php . |
Image | The uploaded file must be an image. |
Comments
Post a Comment