Когда встроенные валидаторы Yii2 заканчиваются, а данные требуют особой проверки, наступает время кастомных валидаторов. Это как собрать собственный инструмент, который идеально подходит под вашу задачу. В этой статье мы разберём, как создавать такие валидаторы, какие есть способы и когда что использовать. Поехали!

Digital-студия WNDER

Ссылка на оригинальную статью

Зачем нужны кастомные валидаторы?

В Yii2 уже есть куча готовых валидаторов: проверка email, длины строк, чисел, дат и так далее. Но иногда нужно что-то своё. Например, проверить, что строка не содержит определённых слов, или что число делится на 3 без остатка. Вот тут и приходят на помощь кастомные валидаторы.

Представьте, что вы пишете форму обратной связи, и вам нужно убедиться, что пользователь не вставил в поле ссылку на спам-сайт. Встроенными средствами это не сделать. Или, скажем, у вас есть поле «ИНН», и вам нужно проверить его по алгоритму. Всё это — задачи для кастомных валидаторов.

Способы создания кастомных валидаторов

В Yii2 есть три основных способа создать свой валидатор. Каждый из них подходит для разных ситуаций. Давайте разберём каждый.

Способ Когда использовать Сложность
Анонимная функция Простая проверка, которая используется только в одной модели Низкая
Метод модели Проверка, которая логично принадлежит модели Низкая
Отдельный класс валидатора Сложная логика, переиспользуемая в разных моделях Средняя

Анонимная функция: быстро и просто

Самый простой способ — использовать анонимную функцию прямо в правиле валидации. Это удобно, когда проверка маленькая и не требует переиспользования.

public function rules()
{
    return [
        ['username', function ($attribute, $params) {
            if (strpos($this->$attribute, 'admin') !== false) {
                $this->addError($attribute, 'Имя не должно содержать слово "admin"');
            }
        }],
    ];
}

Здесь мы проверяем, что имя пользователя не содержит подстроку «admin». Если содержит — добавляем ошибку. Всё просто и работает.

Но у этого способа есть минус: если такая проверка понадобится в другой модели, придётся копировать код. Поэтому для переиспользуемых проверок лучше использовать другие способы.

Метод модели: логика внутри модели

Если проверка тесно связана с моделью, можно вынести её в отдельный метод модели. Это делает код чище и понятнее.

public function rules()
{
    return [
        ['phone', 'validatePhone'],
    ];
}

public function validatePhone($attribute, $params)
{
    $phone = preg_replace('/[^0-9]/', '', $this->$attribute);
    if (strlen($phone) !== 11) {
        $this->addError($attribute, 'Телефон должен содержать 11 цифр');
    }
}

В этом примере мы проверяем, что номер телефона (после очистки от лишних символов) содержит ровно 11 цифр. Метод validatePhone вызывается автоматически, когда Yii2 валидирует атрибут phone.

Этот способ удобен тем, что логика проверки находится рядом с моделью, и её легко найти. Но если модель разрастётся, метод может потеряться среди других методов.

Отдельный класс валидатора: мощь и переиспользование

Когда проверка сложная и нужна в нескольких местах, лучше создать отдельный класс валидатора. Такой класс должен наследоваться от yii\validators\Validator и реализовывать метод validateAttribute() или validateValue().

Давайте создадим валидатор, который проверяет, что строка является корректным ИНН (для физических лиц).

namespace app\validators;

use yii\validators\Validator;

class InnValidator extends Validator
{
    public function validateAttribute($model, $attribute)
    {
        $value = $model->$attribute;
        if (!$this->checkInn($value)) {
            $this->addError($model, $attribute, 'ИНН не является корректным');
        }
    }

    private function checkInn($inn)
    {
        // Очищаем от лишних символов
        $inn = preg_replace('/[^0-9]/', '', $inn);
        if (strlen($inn) !== 12) {
            return false;
        }

        // Проверка контрольных сумм
        $weights = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0];
        $sum = 0;
        for ($i = 0; $i < 10; $i++) {
            $sum += (int)$inn[$i] * $weights[$i];
        }
        $checkDigit = ($sum % 11) % 10;
        if ($checkDigit != (int)$inn[10]) {
            return false;
        }

        $weights2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0];
        $sum2 = 0;
        for ($i = 0; $i < 11; $i++) {
            $sum2 += (int)$inn[$i] * $weights2[$i];
        }
        $checkDigit2 = ($sum2 % 11) % 10;
        return $checkDigit2 == (int)$inn[11];
    }
}

Теперь этот валидатор можно использовать в любой модели:

public function rules()
{
    return [
        ['inn', 'app\validators\InnValidator'],
    ];
}

Класс можно положить в папку validators вашего приложения. Если вы используете базовый шаблон Yii2, то создайте папку common/validators или backend/validators в зависимости от структуры.

Валидация значения без модели

Иногда нужно проверить просто значение, без привязки к модели. Для этого можно переопределить метод validateValue().

class InnValidator extends Validator
{
    public function validateValue($value)
    {
        if (!$this->checkInn($value)) {
            return ['ИНН не является корректным', []];
        }
        return null;
    }
}

Этот метод возвращает null, если значение корректно, или массив с ошибкой, если нет. Такой валидатор можно использовать для проверки данных, полученных, например, из запроса.

Правила и лайфхаки

Правило: Если валидатор использует внешние сервисы (например, API), не забывайте про кэширование результатов. Иначе каждый запрос будет бить по производительности.

Ещё пара советов:

  • Всегда возвращайте понятные сообщения об ошибках. Пользователь должен понимать, что пошло не так.
  • Используйте параметры валидатора. Например, можно добавить свойство min или max, чтобы настраивать поведение.
  • Не забывайте про сценарии. Валидатор может применяться только в определённых сценариях (например, при создании, но не при обновлении).

Пример с параметрами

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

class RangeValidator extends Validator
{
    public $min;
    public $max;

    public function validateAttribute($model, $attribute)
    {
        $value = $model->$attribute;
        if ($value < $this->min || $value > $this->max) {
            $this->addError($model, $attribute, 'Значение должно быть от {min} до {max}', [
                'min' => $this->min,
                'max' => $this->max,
            ]);
        }
    }
}

Использование:

public function rules()
{
    return [
        ['age', 'app\validators\RangeValidator', 'min' => 18, 'max' => 60],
    ];
}

Обратите внимание, что мы используем плейсхолдеры {min} и {max} в сообщении об ошибке. Yii2 подставит реальные значения.

Итоговое резюме

Кастомные валидаторы в Yii2 — это мощный инструмент, который позволяет проверять данные так, как нужно именно вам. Начните с простых анонимных функций для мелких проверок, переходите к методам модели, когда логика усложняется, и создавайте отдельные классы для переиспользуемых валидаторов.

Главное — не бойтесь экспериментировать и писать свои проверки. Практика — лучший способ разобраться.

Если вам нужна помощь с разработкой на Yii2 или другими задачами, обращайтесь в Digital-студию WNDER — мы всегда готовы помочь.

Студия WNDER