Validation is an important part of our application. Formerly we wrote about Laravel’s form requests and how to move your validation logic into them. Now we want to show some various validation issues and how to solve them with custom validation rules.

Creating custom validation rules

With the new make:rule command we can generate our custom rules. Along the constructor, we have two other methods by default.

The first is the passes() method, where we have to implement the logic what determines if the given value is correct or not. The other method is the message(), where we can customize our error message if the validation fails.

If we have a ready to use rule, all we have to do to add it to the value’s identifier what we want to validate. In this case, we have to use an array of validation rules, instead of a string, where we separate them with a | character.

// string version
'email' => 'string|email|max:255',

// array version with the custom rule
'email' => [
    'string',
    'email',
    'max:255',
    new MyCustomRule,
],

As we see, it’s straightforward to add the custom rules we want. Of course, if we need we can pass anything to the constructor, we will see some example of that as well.

Let’s see some common validation issues and their solutions with custom validation rules. We will take a look only the passes() method, but we share the rules, and you can use them if you need!

#1 Checking the user’s password

It’s a good practice to check if the real user is using the app by checking its password. Since the password is hashed, we need to be a bit tricky and use Laravel’s Hash facade.

protected $user;

public function __construct(User $user)
{
    $this->user = $user;
}

public function passes($attribute, $value)
{
    return Hash::check($value, $this->user->password);
}
You can pass the user many ways to the constructor. For example, you can use the auth facade or helper, or you can retrieve the user from the HTTP request object.

#2 Odd or even numbers

If for some reason, we need to check if a number odd or even we can do the following:

// Odd
public function passes($attribute, $value)
{
    return (int) $value % 2 === 1;
}

// Even
public function passes($attribute, $value)
{
    return (int) $value % 2 === 0;
}

#3 Value can be incremented only

In some cases, we need to make sure that the given value is not lower than the current one. For example, if we are versioning some model (like 0.1.0, 0.1.1, etc.), we should ensure the versions are incrementing.

protected $model;

public function __construct($model)
{
    $this->model = $model;
}

public function passes($attribute, $value)
{
    return version_compare($this->model->version, $value, '<');
}
If we are using route-model binding here, we can pass the model we want the constructor easily. Even from our form request, we can use $this->route(‘model’), where the model is the key of the binding.

#4 Value contains specific words

It can be convenient if we can check if a given text contains some particular words or text fragments. It can be useful for example spam detection. An elementary example of that:

protected $words = [
    'foo',
    'bar',
    'baz',
];

public function passes($attribute, $value)
{
    return preg_match('(' . implode('|', $this->words) . ')', $value) === 1);
}

#5 Day must be a weekday

If you are working with dates, mostly Laravel saves your life. Some built-in validation rules do the messy things, but sometimes we need to implement some extra features. For example, we want to restrict the dates only for weekdays and throw an error when the selected day is part of a weekend.

public function passes($attribute, $value)
{
    return (new Carbon($value))->isWeekDay();
}
Note that, we are using Carbon here, what makes the work with dates easier than ever.

Summary

With custom validation rules, we can easily implement any kind of logic we need. It’s a nice way to keep clean the controllers or the form requests and generate customized error messages as well.

You can find the full code of the example validation rules in this repository.