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);
});

 

Ось, власне, і все. Успіхів!