Валидация данных является одной из множества практик в разработке безопасного 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 rockvalidaterulesRule
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 rockvalidatelocaleLocale;
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' => namespacetoCSRF::className(),
'locales' => [
'en' => namespacetoenCSRF::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->isEmpty(false)->validate(''); // output: false
По умолчанию пустыми значениями являются $value === null || $value === [] || $value === ''. Для каждого из правил, существует возможность задать свой обработчик isEmpty:
$config = [
'rules' => [
'custom' => [
'class' => namespacetoCustomRule::className(),
'locales' => [
'en' => namespacetoenCustom::className(),
],
'isEmpty' => function($input){
return $input === '';
}
],
]
];
$v = new Validate($config);
Установка
composer require romeoz/rock-validate:*
А посмотреть?
Существует небольшое демо, которое можно запустить одним из двух способов:
1. Подтянуть из docker registry
docker run -d -p 8080:80 romeoz/vagrant-rock-validate
Демо станет доступно по адресу: http://localhost:8080/
2. Воспользоваться связкой VirtualBox + Vagrant
- Клонировать проект romeOz/vagrant-rock-validate
git clone https://github.com/romeOz/vagrant-rock-validate.git
- Опционально. Чтобы Vagrant прописал в hosts фиктивный hostname (http://www.rock-validate/), требуется установить плагин:
vagrant plugin install vagrant-hostsupdater
Демо станет доступно по следующим адресам: www.rock-validate/ или 192.168.33.35/
Автор: romeOz