Laravel: как добавить новое правило в валидатор

Laravel: как добавить новое правило в валидатор

Ubuntu: 20.02
PHP: 8.0.3
Laravel: 8.36.2

Если заглянем в официальные доки, то найдём там пользовательские объекты правил, замыкания и неявные правила. Но не увидим Validator::extend(). Возможно потому, что фишка далеко не нова и была добавлена задолго до текущей (8-й) версии фреймворка. Недавно снова столкнулся, потому оставлю короткое напоминание о том, как использовать. 

Допустим, мы хотим заставить пользователей использовать пароли посложнее. Т.е. пароль должен включать:

  • как минимум одну заглавную букву
  • как минимум одну цифру
  • как минимум один специальный символ

Плюс пароль должен быть не менее N символов, и, естественно, мы хотим добавить соответствующее сообщение об ошибке.

Начнём с того, что добавим настройку с минимальной длиной пароля в файл config/auth.php:

<?php 

return [
    ...
    'password_min_length' => 10,
    ...
];

 

Для валидации будем использовать регулярное выражение (статья не о регулярках, поэтому здесь разбирать его не буду, если есть необходимость, рекомендую, как минимум, ознакомиться со статьёй в вики). Идём в AppServiceProvider (app/Providers/AppServiceProvider.php) и редактируем метод boot():

<?php
...
class AppServiceProvider extends ServiceProvider
{
    ...
    public function boot()
    {
        $min = config('auth.password_min_length');
        $pattern = '/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{'.$min.',}$/';

        Validator::extend('strong_password',
            function ($attributes, $value, $parameters, $validator) use ($pattern) {
                return is_string($value) && preg_match($pattern, $value);
            }
        );
    }
}

 

Вот, собственно, код метода extend(), который можно посмотреть в vendor/laravel/framework/src/Illuminate/Validation/Factory.php:

/**
 * Register a custom validator extension.
 *
 * @param  string  $rule
 * @param  \Closure|string  $extension
 * @param  string|null  $message
 * @return void
 */
public function extend($rule, $extension, $message = null)
{
    $this->extensions[$rule] = $extension;

    if ($message) {
        $this->fallbackMessages[Str::snake($rule)] = $message;
    }
}

 

Как видим третьим аргументом можно передать и сообщение. Однако, обратите внимание - это fallback, который будет использоваться в случае, если ключ (в нашем случае strong_password) не будет найден в языковах файлах переводов проекта. В общем, можно добавить текст ошибки и здесь, но если сайт планируется мультиязычным или может стать таковым в определённый момент, нам всё равно придётся переводить месседж. Поэтому, разместим текст ошибки там, где ей и место, т.е. в resources/lang/en/validation.php (и аналогичных файлах других языков, если таковые имеются). Допишем пару ключ - значение после переводов из коробки:

<?php

return [
    'accepted' => 'The :attribute must be accepted.',
    ...
    'uuid' => 'The :attribute must be a valid UUID.',

    'strong_password' => 'The :attribute must be at least :min characters, contain at least one upper case letter, at least one digit and at least one special character.',
    ...
];

 

Осталось применить правило в валидации регистрации, скажем так:

$request->validate([
    'name' => 'required|string|max:255',
    'email' => 'required|string|email|max:255|unique:users',
    'password' => 'required|strong_password:'.config('auth.password_min_length').'|confirmed',
]);

 

Но тут нас ждёт сюрприз: если :attribute будет заменён на название поля, то :min в сообщении об ошибке так и останется. Посему добавим в провайдер ещё и replacer:

Validator::replacer('strong_password', function ($message, $attribute, $rule, $parameters) {
    return str_replace(':min', $parameters[0], $message);
});

 

Вот, собственно, и всё. Успехов!