Валидация данных является одной из множества практик в разработке безопасного web-приложения. Даже совсем «юный» разработчик при первом своём знакомстве с html-формой пытается вывести красивое сообщение об ошибке. Что уж говорить про модель в каком-нибудь навороченном фреймворке. А потому…Предлагаю вашему вниманию библиотеку для валидации данных с кастомизацией, интернационализацией и иными «плюшками». Используя известный инструмент Respect/Validation с множеством вбитых по ходу костылей, я в какой-то момент сказал себе: Хватит!
Были поставлены задачи:
- сохранить элегантный синтаксис (сцепной принцип для правил);
- реализовать «лёгкую» и гибкую альтернативу;
- добавить интернационализацию;
- предоставить возможность добавлять свои правила;
- подготовить фундамент для санитизатора — обеспечить единый стиль реализации для обеих библиотек.
Всё до безобразия просто
$v = Validate::length(10, 20, true)->regex('/^[a-z]+$/i'); $v->validate('O’Reilly'); // output: false $v->getErrors(); /* output: [ 'length' => 'value must have a length between 10 and 20', 'regex' => 'value contains invalid characters' ] */ $v->getFirstError(); // output: value must have a length between 10 and 20
Список ошибок представлен в виде ассоциативного массива.
- getErrors() — вывод всего стека ошибок;
- getFirstError() — возвращает первую ошибку;
- getLastError() — возвращает последнюю ошибку.
Правила
Набор правил достаточно широк, ибо с небольшими изменениями перекачивал из стана «конкурента».
Существуют группы правил:
- общего назначения
- строковые
- числовые
- дата и время
- файловой системы
- сетевые
- и др.
Полный список правил
Любой дополнительный каприз реализуется кастомизацией, либо pull request-ом.
Валидация по атрибутам
Для валидации массива/объекта по атрибутам используется метод attributes().
$input = [ 'username' => 'O’Reilly', 'email' => 'o-reilly@site' ]; $attributes = [ 'username' => Validate::required() ->length(2, 20, true) ->regex('/^[a-z]+$/i'), 'email' => Validate::required()->email() ]; $v = Valiadte::attributes($attributes); $v->validate($input); // output: false $v->getErrors(); /* output: [ 'username' => [ 'regex' => 'value contains invalid characters', ], 'email' => [ 'email' => 'email must be valid', ], ] */
Использование одного набора правил для каждого атрибута:
Validate::attributes(Validate::required()->string())->validate($input);
Отрицание правил
Инвертировать поведение правил можно с помощью метода notOf(). В это случае, используется «негативный» шаблон сообщения Locale::MODE_NEGATIVE.
$v = Validate::notOf(Validate::required()); $v->validate(''); // output: true
Данный метод применим, как для правил внутреннего атрибута ['email' => Validate::notOf(Validate::email())], так и для всех атрибутов в целом. Пример:
$input = [ 'email' => 'tom@site', 'username' => '' ]; $attributes = Validate::attributes([ 'email' => Validate::email(), 'username' => Validate::required() ]); $v = Validate::notOf($attributes); $v->validate($input); // output: true
Правило oneOf()
Если хотя бы одно правило неверно, то проверка останавливается. Пример:
$input = 7; $v = Validate::oneOf(Validate::string()->email()); $v->validate($input); // output: false $v->getErrors(); /* output: [ 'string' => 'value must be string' ] */
Для валидации по атрибутам сценарий аналогичен отрицанию:
$input = [ 'email' => 'tom@site', 'username' => '' ]; $attributes = Validate::attributes([ 'email' => Validate::email(), 'username' => Validate::required() ]); $v = Validate::oneOf($attributes); $v->validate($input); // output: false $v->getErrors(); /* output: [ 'email' => [ 'email' => 'email must be valid', ] ] */
Правило when()
Необходимо для реализации условия (тернарная условная операция). Общий синтаксис метода выглядит так:
v::when(v $if, v $then, v $else = null)
Пример:
$v = Validate::when(Validate::equals('Tom'), Validate::numeric()); $v->validate('Tom'); // output false $v->getErrors(); /* output: [ 'numeric' => 'value must be numeric', ] */
Замена плейсхолдеров, сообщений и шаблонов
Многие сообщения об ошибках содержат плейсхолдеры (к примеру, {{name}}), которые заменяются значениями по умолчанию. Заменить на свои не составит труда:
$v = Validate::length(10, 20) ->regex('/^[a-z]+$/i') ->placeholders(['name' => 'username']); $v->validate('O’Reilly'); // output: false $v->getErrors(); /* output: [ 'length' => 'username must have a length between 10 and 20', 'regex' => 'username contains invalid characters', ] */
Аналогично, такой «горячей» замены подлежит и всё сообщение:
$v = Validate::length(10, 20) ->regex('/^[a-z]+$/i') ->messages(['regex' => 'Хьюстон, у нас проблемы!']); $v->validate('O’Reilly'); // output: false $v->getErrors(); /* output: [ 'length' => 'username must have a length between 10 and 20', 'regex' => 'Хьюстон, у нас проблемы!' ] */
В зависимости от заданных аргументов в методе правила, шаблон сообщения подбирается автоматически, но никто не мешает подставить любой другой, который описан в текущей локали. Пример:
$v = Validate::length(10, 20)->templates(['length' => Length::GREATER]); $v->validate('O’Reilly'); // output: false $v->getErrors(); /* output: [ 'length' => 'value must have a length lower than 20', ] */
Интернационализация
На текущий момент времени существуют два словаря сообщений: русский и английский. По умолчанию сообщения об ошибках будут выводится на английском языке. Установить локаль можно через метод locale():
$v = Validate::locale('ru') ->length(10, 20) ->regex('/^[a-z]+$/i'); $v->validate('O’Reilly'); // output: false $v->getErrors(); /* output: [ 'length' => 'значение должно иметь длину в диапазоне от 10 до 20', 'regex' => 'значение содержит неверные символы', ] */
Кастомизация
Создать свои правила можно в два шага.
Шаг #1. Создаём класс с правилом:
use rock\validate\rules\Rule class CSRF extends Rule { public function __construct($compareTo, $compareIdentical = false, $config = []) { $this->parentConstruct($config); $this->params['compareTo'] = $compareTo; $this->params['compareIdentical'] = $compareIdentical; } public function validate($input) { if ($this->params['compareIdentical']) { return $input === $this->params['compareTo']; } return $input == $this->params['compareTo']; } }
Шаг #2. Создаём класс с сообщениями:
use rock\validate\locale\Locale; class CSRF extends Locale { const REQUIRED = 1; public function defaultTemplates() { return [ self::MODE_DEFAULT => [ self::STANDARD => '{{name}} must be valid', self::REQUIRED => '{{name}} must not be empty' ], self::MODE_NEGATIVE => [ self::STANDARD => '{{name}} must be invalid', self::REQUIRED => '{{name}} must be empty' ] ]; } public function defaultPlaceholders($compareTo) { if (empty($compareTo)) { $this->defaultTemplate = self::REQUIRED; } return [ 'name' => 'CSRF-token' ]; } }
Как ранее отмечалось, при использовании правила notOf() будет подставлен шаблон сообщения Locale::MODE_NEGATIVE. Подшаблон же позволяет разнообразить сообщения в зависимости от заданных аргументов в методе правила. По умолчанию Locale::STANDARD.
Профит:
$config = [ 'rules' => [ 'csrf' => [ 'class' => \namespace\to\CSRF::className(), 'locales' => [ 'en' => \namespace\to\en\CSRF::className(), ] ], ] ]; $sessionToken = 'foo'; $requestToken = 'bar'; $v = new Validate($config); $v->csrf($sessionToken)->validate($requestToken); // output: false $v->getErrors(); /* output: [ 'csrf' => 'CSRF-token must be valid', ] */
Таким образом, можно осуществить подмену существующих правил.
Дополнительные возможности
Существует сценарий, когда необходимо пропустить «пустые» значения. К примеру, для полей формы необязательных к заполнению. Для этих целей существует свойство skipEmpty — задаётся реакция для каждого правила на «пустые» значения. Для некоторых правил это свойство выставлено в false (не пропускать), а именно: Required, Arr, Bool, String, Int, Float, Numeric, Object, NullValue, Closure, всех ctype-правил. Пример:
$v = Validate::email(); $v->validate(''); // output: true
Данное поведение можно отменить:
$v->skipEmpty(false)->validate(''); // output: false
По умолчанию пустыми значениями являются $value === null || $value === [] || $value === ''. Для каждого из правил, существует возможность задать свой обработчик isEmpty:
$config = [ 'rules' => [ 'custom' => [ 'class' => \namespace\to\CustomRule::className(), 'locales' => [ 'en' => \namespace\to\en\Custom::className(), ], 'isEmpty' => function($input){ return $input === ''; } ], ] ]; $v = new Validate($config);
Установка
composer require romeoz/rock-validate:*
А посмотреть?
Существует небольшое демо, которое можно запустить с помощью Docker:
docker run --name demo -d -p 8080:80 romeoz/docker-rock-validate
Демо станет доступно по адресу: http://localhost:8080/
