The ultimate guide to Laravel Validation

Published on by

The ultimate guide to Laravel Validation image

Validation is an important part of any web application. It can help to prevent security vulnerabilities, data corruption, and a whole host of other issues that can arise when you're working with user input.

In this article, we're going to take a look at what validation is and why it's important. We'll then compare client-side validation with server-side validation and explore why you should never rely solely on client-side validation in your applications.

We'll then take a look at some handy validation rules that I like to use in my Laravel apps. Finally, we'll take a look at how you can create your own validation rule and test it to make sure it works as expected.

What is Validation?

Validation is the process of checking that data is valid before attempting to use it. This can be anything from checking simple things like whether a required field is present in a request, to more complex checks like whether a field matches a certain pattern or is unique in a database.

Typically, when validating data in a web application, if the data is invalid, you'll want to return an error message to the user.

This can help to prevent security vulnerabilities, data corruption, and improve data accuracy. So we only continue with handling the request if the data is valid.

Remember, no data from a user can be trusted (at least until you've validated it)!

Why is Validation Important?

Validation is important for a number of reasons, including:

Improving Security

One of the most important reasons to validate data in your application is to improve security. By validating the data before you use it, you can reduce the chances of any malicious input being used to attack your application or your users.

Preventing Incorrect Data Being Stored

Imagine a scenario where we expect that a field is an integer, but the user passes a file instead. This could cause all sorts of issues in our application when we try and use that data somewhere else in our application.

As another example, imagine you are building a web application that allows users to vote on polls. The polls can only be voted on between an opens_at time and a closes_at time that's specified on an App\Models\Poll model. What would happen if someone setting up the poll accidentally set the closes_at time to be before the opens_at time? Depending on how you handle this in your application, this could cause all sorts of issues.

By validating the data before it's stored on the model, we can improve the data accuracy in our application and reduce the chances of incorrect data being stored.

Ensuring Correct Artisan Command Input

As well as being able to validate the data passed in HTTP requests, you can also validate your Artisan commands. This can help to prevent a developer from accidentally entering an invalid value and causing issues in your application.

Client-Side Validation vs Server-Side Validation

There are generally two types of validation that you can use in your applications: client-side validation and server-side validation.

Client-Side Validation

Client-side validation is validation that is performed in the browser before the data is sent to the server. It might be implemented using JavaScript or maybe even using HTML attributes.

For example, we can add some simple validation to a number field in HTML to make sure that the user enters a number between 1 and 10:

<input type="number" name="quantity" min="1" max="10" required>

There are four separate parts to this input field that are useful for client-side validation purposes:

  • type="number": This tells the browser that the input should be a number. On most browsers, this will prevent the user from entering anything other than a number. On a mobile device, it may even bring up a number pad instead of a regular keyboard which is great for user experience.
  • min="1": This tells the browser that the number entered must be at least 1.
  • max="10": This tells the browser that the number entered must be at most 10.
  • required: This tells the browser that the field is required and must be filled in before the form can be submitted.

In most browsers, if the user tries to submit the form with an invalid value (or no value at all), the browser will prevent the form from being submitted and show an error message or hint to the user.

This is great for guiding the user and improving the general user experience of your application. But that's all this should be treated as: a guide. You should never rely on client-side validation as the only form of validation in your application.

If someone were to open up the developer tools in their browser, they could easily remove and bypass the client-side validation that you have in place.

As well as this, it's important to remember that when malicious users are trying to attack your application, they'll generally be using automated scripts to send requests directly to your server. This means the client-side validation that you have in place will be bypassed.

Server-Side Validation

Server-side validation is the validation that you run in your application's backend on your server. In the context of Laravel applications, this is typically the validation that you run in your controllers or form request classes.

Since the validation sits on your server and can't be changed by the user, it's the only way to really ensure that the data being sent to your server is valid.

So it's important to always have server-side validation in place in your applications. In an ideal world, every single field that you attempt to read from a request should be validated before you try to use it for performing any business logic.

How Laravel Handles Validation

Now that we've got an understanding of what validation is and why it's important, let's take a look at how to use it in Laravel.

If you've been working with Laravel for a while, you'll know that Laravel has an amazing validation system built into the framework. So it's really easy to make a start with validation in your applications.

There are several common ways to validate data in Laravel, but we're going to look at the two most common ways:

  • Validating data manually
  • Validating data using form request classes

Validating Data Manually

To validate data manually (such as in a controller method), you can use the Illuminate\Support\Facades\Validator facade and call the make method.

We can then pass two parameters to the make method:

  • data - The data that we want to validate
  • rules - The rules that we want to validate the data against

Side note: The make method also accepts two optional parameters: messages and attributes. These can be used to customize the error messages that are returned to the user, but we won't be covering them in this article.

Let's look at an example of how you might want to validate two fields:

use Illuminate\Support\Facades\Validator;
 
$validator = Validator::make(
data: [
'title' => 'Blog Post',
'description' => 'Blog post description',
],
rules: [
'title' => ['required', 'string', 'max:100'],
'description' => ['required', 'string', 'max:250'],
]
);

We can see in the example above that we're validating two fields: title and body. We've hardcoded the values of the two fields to make the examples clearer, but in a real-life project, you'd typically fetch these fields from the request instead. We're checking that the title field is set, is a string, and has a maximum length of 100 characters. We're also checking that the description field is set, is a string, and has a maximum length of 250 characters.

After creating the validator, we can then call methods on the Illuminate\Validation\Validator instance that is returned. For example, to check if the validation has failed, we can call the fails method:

$validator = Validator::make(
data: [
'title' => 'Blog Post',
'description' => 'Blog post description',
],
rules: [
'title' => ['required', 'string', 'max:100'],
'description' => ['required', 'string', 'max:250'],
]
);
 
if ($validator->fails()) {
// One or more of the fields failed validation.
// Handle it here...
}

Similarly, we can also call the validate method on the validator instance:

Validator::make(
data: [
'title' => 'Blog Post',
'description' => 'Blog post description',
],
rules: [
'title' => ['required', 'string', 'max:100'],
'description' => ['required', 'string', 'max:250'],
]
)->validate();

This validate method will Illuminate\Validation\ValidationException if the validation fails. Laravel will automatically handle this exception depending on the type of request that's being made (assuming you haven't changed the default exception handling in your application). If the request is a web request, Laravel will redirect the user back to the previous page with the errors in the session for you to display. If the request is an API request, Laravel will return a 422 Unprocessable Entity response with a JSON representation of the validation errors like so:

{
"message": "The title field is required. (and 1 more error)",
"errors": {
"title": [
"The title field is required."
],
"description": [
"The description field is required."
]
}
}

Validating Data Using Form Request Classes

The other way that you'll usually validate data in your Laravel apps is through the use of form request classes. Form request classes are classes that extend Illuminate\Foundation\Http\FormRequest and are used to run authorization checks and validation on incoming requests.

I find they're a great way to keep your controller methods clean because Laravel will automatically run the validation against data passed in the request before our controller method's code is run. So we don't need to remember to run any methods on the validator instance ourselves.

Let's take a look at a simple example. Imagine we have a basic App\Http\Controllers\UserController controller with a store method that allows us to create a new user:

declare(strict_types=1);
 
namespace App\Http\Controllers;
 
use App\Http\Requests\Users\StoreUserRequest;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Hash;
 
final class UserController extends Controller
{
public function store(StoreUserRequest $request): RedirectResponse
{
User::create([
'name' => $request->validated('name'),
'email' => $request->validated('email'),
'password' => Hash::make($request->validated('password')),
]);
 
return redirect()
->route('users.index')
->with('success', 'User created successfully.');
}
}

In the controller method, we can see that we're accepting an App\Http\Requests\Users\StoreUserRequest form request class (which we'll look at next) as a method parameter. This will indicate to Laravel that we want the validation in this request class to automatically be run when calling this method via an HTTP request.

We're then using the validated method on the request instance within our controller method to grab the validated data from the request. This means it will only return the data that has been validated. For example, if we were to try and save a new profile_picture field in the controller, it'd have to also be added to the form request class. Otherwise, it wouldn't be returned by the validated method and so $request->validated('profile_picture') would return null.

Now let's take a look at the App\Http\Requests\Users\StoreUserRequest form request class:

declare(strict_types=1);
 
namespace App\Http\Requests\Users;
 
use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
 
final class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->user()->can('create', User::class);
}
 
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'email', Rule::unique('users')],
'password' => [Password::defaults()],
];
}
}

We can see the request class contains two methods:

  • authorize: This method is used to determine if the user is authorized to make the request. If the method returns false, a 403 Forbidden response will be returned to the user. If the method returns true, the validation rules will be run.
  • rules: This method is used to define the validation rules that should be run on the request. The method should return an array of rules that should be run on the request.

In the rules method, we're specifying that the name field must be set, must be a string, and must have a maximum length of 100 characters. We're also specifying that the email field must be set, must be an email, and must be unique in the users table (on the email column). Finally, we're specifying that the password field must be set and must pass the default password validation rules that we've set (we'll take a look at password validation later).

As you can see, this is a great way to separate the validation logic from the controller logic, and I find it makes the code easier to read and maintain.

Handy Validation Rules in Laravel

As I've already mentioned, the Laravel validation system is really powerful and makes adding validation to your applications a breeze.

In this section, we're going to take a quick look at some handy validation rules that I like and think most of you will find useful in your applications.

If you're interested in checking out all the rules that are available in Laravel, you can find them in the Laravel documentation: https://laravel.com/docs/11.x/validation

Validating Arrays

A common type of validation you'll need to run will be to validate arrays. This could be anything from validating that an array of IDs passed are all valid, to validating that an array of objects passed in a request all have certain fields.

Let's take a look at an example of how to validate an array and then we'll discuss what's being done:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
 
Validator::make(
data: [
'users' => [
[
'name' => 'Eric Barnes',
'email' => 'eric@example.com',
],
[
'name' => 'Paul Redmond',
'email' => 'paul@example.com',
],
[
'name' => 'Ash Allen',
'email' => 'ash@example.com',
],
],
],
rules: [
'users' => ['required', 'array'],
'users.*' => ['required', 'array:name,email'],
'users.*.name' => ['required', 'string', 'max:100'],
'users.*.email' => ['required', 'email', 'unique:users,email'],
]
);

In the example above, we're passing an array of objects, each with a name and email field.

For the validation, we're first defining that the users field is set and is an array. We're then specifying that each item of the array (targeted using users.*) is an array containing name and email fields.

We're then specifying that the name field (targeted using users.*.name) must be set, must be a string, and cannot be longer than 100 characters. We're also specifying that the email field (targeted using users.*.email) must be set, must be an email, and must be unique in the users table on the email column.

By being able to use the * wildcard in the validation rules, we can easily validate arrays of data in our applications.

Validating Dates

Laravel provides several handy date validation rules that you can use. First, to validate that a field is a valid date, you can use the date rule:

$validator = Validator::make(
data: [
'opens_at' => '2024-04-25',
],
rules: [
'opens_at' => ['required', 'date'],
]
);

If you'd prefer to check that a date is in a specific format, you can use the date_format rule:

$validator = Validator::make(
data: [
'opens_at' => '2024-04-25',
],
rules: [
'opens_at' => ['required', 'date_format:Y-m-d'],
]
);

It's likely that you may want to check that a date is before or after another date. For example, let's say you have opens_at and closes_at fields in your request and you want to ensure that closes_at is after opens_at and that opens_at is after or equal to today. You can use the after rule like so:

$validator = Validator::make(
data: [
'opens_at' => '2024-04-25',
'closes_at' => '2024-04-26',
],
rules: [
'opens_at' => ['required', 'date', 'after:today'],
'closes_at' => ['required', 'date', 'after_or_equal:opens_at'],
]
);

In the example above, we can see that we've passed today as an argument to the after rule for the opens_at field. Laravel will attempt to convert this string to a valid DateTime object using the strtotime function and compare it against that.

For the closes_at field, we've passed opens_at as an argument to the after_or_equal rule. Laravel will automatically detect that this is another field that's being validated and will compare the two fields against each other.

Similarly, Laravel also provides before and before_or_equal rules that you can use to check that a date is before another date:

$validator = Validator::make(
data: [
'opens_at' => '2024-04-25',
'closes_at' => '2024-04-26',
],
rules: [
'opens_at' => ['required', 'date', 'before:closes_at'],
'closes_at' => ['required', 'date', 'before_or_equal:2024-04-27'],
]
);

Validating Passwords

As web developers, it's our job to try and help our users stay safe online. One way we can do this is by trying to promote good password practices in our applications, such as requiring a password to be a certain length, contain certain characters, etc.

Laravel makes it easy for us to do this by providing an Illuminate\Validation\Rules\Password class that we can use to validate passwords.

It comes with several methods that we can chain together to build up the password validation rules that we want. For example, let's say we want our users' passwords to fit the following criteria:

  • Be at least 8 characters long
  • Contain at least one letter
  • Contain at least one uppercase and one lowercase letter
  • Contain at least one number
  • Contain at least one symbol
  • Not be a compromised password (i.e. not in the Have I Been Pwned database that has records of exposed passwords from data breaches in other systems)

Our validation might look something like so:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
 
$validator = Validator::make(
data: [
'password' => 'my-password-here'
'password_confirmation' => 'my-password-here',
],
rules: [
'password' => [
'required',
'confirmed',
Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(),
],
],
);

As we can see in the example, we're using the chainable methods to build up the password validation rules that we want. But what happens if we're using these rules in several different places (e.g. - registering, resetting a password, updating a password on your account page, etc.) and we need to change this validation to enforce a minimum of 12 characters? We'd need to go through everywhere these rules are used and update them.

To make this easier, Laravel allows us to define a default set of password validation rules that we can use throughout our application. We can do this by defining a default set of rules in the boot method of our App\Providers\AppServiceProvider like so using the Password::defaults() method:

namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Password::defaults(static function (): Password {
return Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised();
});
}
}

After doing this, we can now call Password::defaults() in our validation rules and the rules we've specified in the AppServiceProvider will be used:

'password' => ['required', 'confirmed', Password::defaults()],

Validating colors

Almost every project I've ever worked on has had some form of color picker in it. Whether it's for a user to pick a color for their profile, a background color for a section of a page, or something else, it's something that comes up a lot.

In the past, I've had to use regular expressions (which I'll admit I didn't really understand a huge amount) to validate that the color was a valid color in hexadecimal format (e.g. - #FF00FF). However, Laravel now has a handy hex_color that you can use instead:

use Illuminate\Support\Facades\Validator;
 
Validator::make(
data: [
'color' => '#FF00FF',
],
rules: [
'color' => ['required', 'hex_color'],
]
);

Validating Files

If you're uploading files to your application through your server, you'll want to validate that the file is valid before you attempt to store it. As you'd imagine, Laravel provides several file validation rules that you can use.

Let's say you want to allow a user to upload a PDF (.pdf) or Microsoft Word (.docx) file. The validation might look something like so:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;
 
Validator::validate($request->all(), [
'document' => [
'required',
File::types(['pdf', 'docx'])
->min('1kb')
->max('10mb'),
],
]);

In the code example, we can see that we're validating the file type and also setting some minimum and maximum file size limits. We're using the types method to specify the file types that we want to allow.

The min and max methods can also accept a string with other suffixes indicating the file size units. For example, we could also use:

  • 10kb
  • 10mb
  • 10gb
  • 10tb

Additionally, we also have the ability to ensure that the file is an image using the image method on the Illuminate\Validation\Rules\File class:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;
 
Validator::validate($input, [
'photo' => [
'required',
File::image()
->min('1kb')
->max('10mb')
->dimensions(Rule::dimensions()->maxWidth(500)->maxHeight(500)),
],
]);

In the example above, we're validating that the file is an image, setting some minimum and maximum file size limits, and also setting some maximum dimensions (500 x 500px).

You might want to take a different approach to file uploads in your application. For example, you may want to upload directly from the user's browser to cloud storage (such as S3). If you'd prefer to do this, you might want to check out my Uploading Files in Laravel Using FilePond article that shows you how to do this, the different approach to validation that you might want to take, and how to test it.

Validating a Field Exists in the Database

Another common check you may want to make is to ensure that a value exists in the database.

For example, let's imagine you have some users in your application and you have created a route so you can bulk assign them to a team. So in your request, you might want to validate that the user_ids that are passed in the request all exist in the users table.

To do this, you can use the exists rule and pass the table name that you want to check the value exists in:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
 
Validator::make(
data: [
'users_ids' => [
111,
222,
333,
],
],
rules: [
'user_ids' => ['required', 'array'],
'user_ids.*' => ['required', 'exists:users,id'],
]
);

In the example above, we're checking that each of the IDs passed in the user_ids array exists in the users table on the id column.

This is a great way to ensure that the data you're working with is valid and exists in the database before you try and use it.

If you'd like to take this a step further, you can apply a where clause to the exists rule to further filter the query that's run:

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
 
Validator::make(
data: [
'users_ids' => [
111,
222,
333,
],
],
rules: [
'user_ids' => ['required', 'array'],
'user_ids.*' => ['required', Rule::exists('users')->where(static function (Builder $query): void {
$query->where('is_verified', true);
})],
]
);

In the example above, we're checking that each of the IDs passed in the user_ids array exists in the users table on the id column and that the users' is_verified column is set to true. So if we were to pass the ID of a user that isn't verified, the validation would fail.

Validating a Field is Unique in the Database

Similar to the exists rule, Laravel also provides a unique rule that you can use to check that a value is unique in the database.

For example, let's say you have a users table and you want to ensure the email field is unique. You can use the unique rule like so:

use Illuminate\Support\Facades\Validator;
 
Validator::make(
data: [
'email' => 'mail@ashallendesign.co.uk',
],
rules: [
'email' => ['required', 'email', 'unique:users,email'],
]

In the example above, we're checking that the email field is set, is an email, and is unique in the users table on the email column.

But what would happen if we tried to use this validation on a profile page where a user could update their email address? The validation would fail because a row exists on the users table with the email address that the user is trying to update to. In this scenario, we can use the ignore method to ignore the user's ID when checking for uniqueness:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
 
Validator::make(
data: [
'email' => 'mail@ashallendesign.co.uk',
],
rules: [
'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)],
]

If you do choose to use the ignore method, you should make sure to read this warning from the Laravel documentation:

"You should never pass any user controlled request input into the ignore method. Instead, you should only pass a system generated unique ID such as an auto-incrementing ID or UUID from an Eloquent model instance. Otherwise, your application will be vulnerable to an SQL injection attack."

There might also be times when you want to add additional where clauses to the unique rule. You might want to do this to ensure that an email address is unique for a specific team (meaning another user in a different team can have the same email). You can do this by passing a closure to the where method:

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
 
Validator::make(
data: [
'email' => 'mail@ashallendesign.co.uk',
],
rules: [
'email' => [
'required',
'email',
Rule::unique('users')
->ignore($user->id)
->where(fn (Builder $query) => $query->where('team_id', $teamId));
)],
],
);

Creating Your Own Validation Rule

Although Laravel comes with a huge number of built-in validation rules, there will likely be times when you need to create your custom validation rule to fit a specific use case.

Thankfully, this is also super easy to do in Laravel!

Let's take a look at how we can build our custom validation rule, how to use it, and then how to write tests for it.

For the purposes of the article, we don't care too much about what we're validating. We just want to look at the general structure of creating a custom validation rule and how to test it. So we'll create a simple rule that checks if a string is a palindrome.

Just in case you don't know, a palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backwards. For example, "racecar" is a palindrome because if you reversed the string, it would still be "racecar". Whereas, "laravel" is not a palindrome because if you reversed the string, it would be "levaral".

To get started, we'll first create a new validation rule by running the following command in our project route:

php artisan make:rule Palindrome

This should have created a new App/Rules/Palindrome.php file for us:

namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class Palindrome implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
//
}
}

Laravel will automatically call the validate method when the rule is run. The method takes three parameters:

  • $attribute: The name of the attribute being validated.
  • $value: The value of the attribute being validated.
  • $fail: A closure that you can call if the validation fails.

So we can add our validation logic inside the validate method like so:

declare(strict_types=1);
 
namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
 
final readonly class Palindrome implements ValidationRule
{
/**
* Run the validation rule.
*
* @param Closure(string): PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($value !== strrev($value)) {
$fail('The :attribute must be a palindrome.');
}
}
}

In the rule above, we're simply checking whether the value passed to the rule is the same as the value reversed. If it's not, we call the $fail closure with an error message. This will cause the validation for the field to fail. If the validation passes, then the rule will do nothing and we can continue with our application.

Now that we've created our rule, we can use it in our application like so:

use App\Rules\Palindrome;
use Illuminate\Support\Facades\Validator;
 
$validator = Validator::make(
data: [
'word' => 'racecar',
],
rules: [
'word' => [new Palindrome()],
]
);

Although this is a simple rule that we've created for demonstration purposes, hopefully, this gives you an idea of how you could build more complex rules for your applications.

Testing Your Own Validation Rule

Just like any other code in your application, it's important to test your validation rules to make sure they work as expected. Otherwise, you may be risking using a rule that doesn't work as you expect it to.

To get an understanding of how to do this, let's take a look at how we can test the palindrome rule that we created in the previous section.

For this particular rule, there are two scenarios that we want to test:

  • The rule passes when the value is a palindrome.
  • The rule fails when the value is not a palindrome.

In more complex rules, you may have more scenarios, but for the purposes of this article, we're keeping it simple.

We'll create a new test file in our tests/Unit/Rules directory called PalindromeTest.php.

Let's take a look at the test file and then we'll discuss what's being done:

declare(strict_types=1);
 
namespace Tests\Unit\Rules;
 
use App\Rules\PalindromeNew;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
 
final class PalindromeTest extends TestCase
{
#[Test]
#[DataProvider('validValues')]
public function rule_passes_with_a_valid_value(string $word): void
{
(new PalindromeNew())->validate(
attribute: 'word',
value: $word,
fail: fn () => $this->fail('The rule should pass.'),
);
 
// We got to this point without any exceptions, so the rule passed.
$this->assertTrue(true);
}
 
#[Test]
#[DataProvider('invalidValues')]
public function rule_fails_with_an_invalid_value(string $word): void
{
(new PalindromeNew())->validate(
attribute: 'word',
value: $word,
fail: fn () => $this->assertTrue(true),
);
}
 
public static function validValues(): array
{
return [
['racecar'],
['radar'],
['level'],
['kayak'],
];
}
 
public static function invalidValues(): array
{
return [
['laravel'],
['eric'],
['paul'],
['ash'],
];
}
}

In the test file above, we've defined two tests: rule_passes_with_a_valid_value and rule_fails_with_an_invalid_value.

As the test names suggest, the first test ensures that the rule passes when the value is a palindrome, and the second test ensures that the rule fails when the value is not a palindrome.

We're using the PHPUnit\Framework\Attributes\DataProvider attribute to provide the test with a list of valid and invalid values to test against. This is a great way of keeping your tests clean and being able to check multiple values with the same test. For example, if someone was to add a new valid value to the validValues method, the test would automatically run against that value.

In the rule_passes_with_a_valid_value test, we're calling the validate method on the rule with a valid value. We've passed a closure to the fail parameter (this is the parameter that you call if the validation fails inside the rule). We've specified that if the closure is executed (i.e. the validation fails), then the test should fail. If we make it to the end of the test without the closure being executed, then we know the rule passed and can add a simple assertion assertTrue(true) to pass the test.

In the rule_fails_with_an_invalid_value test, we're doing the same as the first test, but this time we're passing an invalid value to the rule. We've specified that if the closure is executed (i.e. the validation fails), then the test should pass because we're expecting the closure to be called. If we get to the end of the test without the closure being executed then no assertions will have been performed and PHPUnit should trigger a warning for us. However, if you'd prefer to be more explicit and make sure the test fails rather than just giving an error, you may want to take a slightly different approach to writing the test.

Conclusion

In this article, we've taken a look at what validation is and why it's important. We've compared client-side validation with server-side validation and explored why client-side validation should never be used as the only form of validation in your applications.

We've also taken a look at some handy validation rules that I like to use in my Laravel applications. Finally, we've explored how you can create your own validation rule and test it to make sure it works as expected.

Hopefully, you should now feel confident enough to start using more validation to improve the security and reliability of your applications.

Ashley Allen photo

I am a freelance Laravel web developer who loves contributing to open-source projects, building exciting systems, and helping others learn about web development.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with Vue and Livewire stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Get your ideas to market faster with a developer data platform built on the leading modern database. Support transactional, search, analytics, and mobile use cases while using a common query interface and the data model developers love.

MongoDB

The latest

View all →
Behind the Code: A Discussion with Backend Experts including Taylor Otwell image

Behind the Code: A Discussion with Backend Experts including Taylor Otwell

Read article
Artisan `make` Commands Automatically Chop `.php` Extension in Laravel 11.12 image

Artisan `make` Commands Automatically Chop `.php` Extension in Laravel 11.12

Read article
The June 2024 Laravel Worldwide Meetup is Today image

The June 2024 Laravel Worldwide Meetup is Today

Read article
A Lightweight Cart Package for Laravel image

A Lightweight Cart Package for Laravel

Read article
SQLite Studio is a SQLite Database Explorer image

SQLite Studio is a SQLite Database Explorer

Read article
Running a Single Test, Skipping Tests, and Other Tips and Tricks image

Running a Single Test, Skipping Tests, and Other Tips and Tricks

Read article