Изображение взято с сайта Michiana Stransportation (Bike Shops)


Если вы еще не в курсе, что такое Kontrolio, предлагаю прочесть первую часть — «Держите свои данные под контролем». Вкратце, это моя библиотека для валидации данных, написанная на PHP.

В предыдущей статье я обещал написать сравнение своей собственной библиотеки с другими имеющимися решениями, так что сегодня мы рассмотрим валидацию с помощью Aura.Filter, Respect Validation, Sirius Validation и Valitron.


Давайте представим, что у нас в разработке есть некий публичный сервис, который предполагает регистрацию пользователей для полного доступа ко всем функциям. Таким образом, форма регистрации будет содержать следующие поля:


  1. name. Должно содержать ровно два слова, где первое — имя пользователя, а второе — фамилия.
  2. login. Если значение передано, то оно должно только латинские буквы, дефисы и нижнее подчеркивание.
  3. email. Должно содержать валидный адрес электронной почты.
  4. password. Должен быть установлен и иметь длину не более 64 символов.
  5. agreed. Типичный флажок, который пользователь должен установить, чтобы подтвердить своё согласие с условиями сервиса.

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


$data = [
    'name'  => 'Альберт', // Должно быть два слова
    'login' => '@lbert', // "Запрещенный" символ @
    'email' => 'что-то не то', // Здесь должен быть e-mail
    'password' => '' // Пароль вообще не указан
    // 'agreed' нет в массиве, потому что пользователь не установил флажок
];

Aura.Filter


Валидация с использованием Aura.Filter начинается с фабрики фильтров. Нам необходимо создать так называемый «фильтр субъекта», так как мы будет валидировать массив, а не индивидуальное значение.


Определяем правила


use Aura\Filter\FilterFactory;

$filter = (new FilterFactory)->newSubjectFilter();

$filter->validate('name')
    ->isNotBlank()
    ->is('two_words')
    ->setMessage('Имя должно состоять из двух слов.');

$filter->validate('login')
    ->isBlankOr('alnum')
    ->setMessage('Если вы указываете логин, он должен содержать только латинские символы.');

$filter->validate('email')
    ->isNotBlank()
    ->is('email')
    ->setMessage('Пожалуйста, напишите корректный адрес эл. почты.');

$filter->validate('password')
   ->isNotBlank()
   ->is('strlenMax', 64)
   ->setMessage('Пожалуйста, напишите пароль.');

$filter->validate('agreed')
    ->is('callback', function($subject, $field) {
   return $subject->{$field} === true;    
})->setMessage('Вам необходимо согласиться с условиями сервиса.');

Как видите, описание правил достаточно простое. Aura.Filter предоставляет целый набор полезных правил «из коробки» и некоторые из них были использованы в примере выше:


  1. метод isNotBlank. Указывает, что поле не может иметь пустое значение.
  2. alnum. Это правило допускает только латинские буквы.
  3. email. И так понятно :)
  4. strlenMax. Указывает, что поле не может превышать длину, указанную вторым аргументом метода is.
  5. callback. Этот тип правила похож на замыкания из Kontrolio. Он позволяет определить правило в виде замыкания. В это замыкание Aura.Filter передает «субъект», наш массив данных из формы, и поле, в данном случае agreed.

Наверняка вы заметили, что я не указал правило two_words. Естественно, в Aura.Filter такого правила нет, поэтому нам необходимо его создать. Как гласит документация, это делается с помощью отдельного класса для правила:


/**
 * Правило, которое валидирует имя пользователя.
 * Имя пользователя состоит из двух слов: имени и фамилии, разделенных одним пробелом.
 */
class UserNameRule
{
    /**
     * Валидирует имя пользователя.
     *
     * @param object|array $subject
     * @param string $field
     * @param int $max
     *
     * @return bool
     */
    public function __invoke($subject, $field, $max = null)
    {
        $value = $subject->{$field};

        if ( ! is_scalar($value)) {
            return false;
        }

        return (bool) preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $value);
    }
}

The second step is to let the filter factory know about our new rule. It’s done by passing the first argument as an array of rules to the filter factory:


Следущий шаг — уведомить Aura.Filter о том, что мы создали новое правило и хотим его использовать. Это делается с помощью передачи массива правил в первый аргумент фабрики:


use Aura\Filter\FilterFactory;

$rules = [
    'two_words' => function() {
        return new UserNameRule;
    }
];

$filter = (new FilterFactory($rules))->newSubjectFilter();

Теперь наше правило two_words может быть использовано так же, как и любое другое правило из стандартной поставки.


Обратная связь


Как вы помните, входящие данные, которые мы валидируем, — полностью невалидны, потому что каждое поле содержит некорректное значение или не содержит его вовсе. Поэтому предполагается, что в результате валидации мы получим ошибки и соответствующие сообщения о них.


Валидируем с Aura.Filter мы следующим образом:


$valid = $filter->apply($data);

if ( ! $valid) {
    $failures = $filter->getFailures();
    $messages = $failures->getMessages();
}

В $messages записывается массив, поэтому для вывода сообщений нам потребуется два вложенных foreach:


<ul class="errors">
    <?php
    foreach ($messages as $field => $errors) {
       foreach ($errors as $error) {
            printf('<li class="error">%s</li>', $error);
        }
    }
    ?>
</ul>

Respect Validation


Вторая библиотека, использованная мной в сравнении, — относительно популярное решение под названием Respect Validation. Раз люди ей доверяют, думаю, там есть что посмотреть.


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


use Respect\Validation\Validator as v;

$data = [
    'name'  => 'Альберт', // Должно быть два слова
    'login' => '@lbert', // "Запрещенный" символ @
    'email' => 'что-то не то', // Здесь должен быть e-mail
    'password' => '' // Пароль вообще не указан
    // 'agreed' нет в массиве, потому что пользователь не установил флажок
];

Определяем правила


Как и в случае с Aura.Filter, нам необходимо собственное правило валидации для имени пользователя, поэтому давайте с него и начнем:


namespace MyNamespace;

use Respect\Validation\Rules\AbstractRule;

class UserNameRule extends AbstractRule
{
    public function validate($input)
    {
        return (bool) preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $input);
    }
}

Внешнее API правил практически идентично Aura.Filter, только используется метод validate() вместо магии __invoke(). Мне оно, это API, показалось более простым и понятным. Ну, и к Kontrolio оно ближе :)


В документации я не нашел об этом упоминания, тем не менее помимо самого правила для него необходимо создать собственный тип исключения. Название класса исключения должно состоять из имени класса правила и постфикса Exception.


use Respect\Validation\Exceptions\NestedValidationException;

class UserNameRuleException extends NestedValidationException
{
    //
}

Ну и наконец-то мы можем провалидировать наши данные. Для начала мы передаем валидатору наше новое правило, чтобы он узнал о нем, и мы смогли его использовать в дальнейшем. В Respect Validation это делается вызовом метода with() с передачей пространства имен, в котором находятся нестандартные правила.


v::with('MyNamespace\\');

Теперь все нестандартные правила, находящиеся в пространстве имен MyNamespace, будут «опознаны» валидатором. Следующий шаг — описать необходимые правила и выполнить валидацию.


v::attribute('name', v::userNameRule())
 ->attribute('login', v::alnum('-_'))
 ->attribute('email', v::email())
 ->attribute('password', v::notEmpty()->stringType()->length(null, 64))
 ->attribute('agreed', v::trueVal())
 ->assert((object) $data);

Обратите внимание на то, как мы применяем наше правило к атрибуту name. Здесь название класса правило трансформировалось в название метода валидатора. Остальные правила, в общем-то, интуитивно понятны.


Отдельно стоит сказать о том, зачем мы приводим массив $data к объекту. Дело в том, что Respect Validation принимает на вход объекты, а не массивы. Это следует учесть при разработке с использованием данной библиотеки.


Обратная связь


В отличие от Aura.Filter валидатор Respect выбрасывает исключение, когда валидация провалена. И в этом исключении содержатся сообщения об ошибках валидации. Поэтому пример, который только что был показан, должен быть записан следующим образом:


try {
    v::with('RespectValidationExample\\');
    v::attribute('name', v::userNameRule())
     ->attribute('login', v::alnum('-_'))
     ->attribute('email', v::email())
     ->attribute('password', v::notEmpty()->stringType()->length(null, 64))
     ->attribute('agreed', v::trueVal())
     ->assert((object) $data);
} catch (NestedValidationException $ex) {
    $messages = $ex->getMessages();
}

Используя getMessages(), мы получим плоский массив всех сообщений, который валидатор собрал в процессе валидации. Задампив массив, мы получим примерно такой результат:


array(5) {
 [0] => string(29) “Data validation failed for %s”
 [1] => string(60) “login must contain only letters (a-z), digits (0–9) and “-_””
 [2] => string(25) “email must be valid email”
 [3] => string(26) “password must not be empty”
 [4] => string(32) “Attribute agreed must be present”
}

Можно поменять сообщения на свои собственные. Возможно, я как-то не так понял эту библиотеку, но мне этот процесс не показался таким уж очевидным: необходимо использовать метод findMessages() на обработанном исключении, в котором вы определяете сообщения не для атрибутов, а для правил.


$ex->findMessages([
    'userNameRule' => 'Имя пользователя должно состоять из двух слов.',
    'alnum' => 'Ваш логин нам не нравится.',
    'email' => 'Вы явно не хотите давать нам свой e-mail.',
    'notEmpty' => 'Ну и где же ваш пароль?',
    'agreed' => 'Жаль, что вы не согласны.'
]);

Не знаю, в чем ошибка, но есть пара вещей, которые я так и не понял. Вот что мы получим, определив правила вышеуказанным способом:


array(5) {
 [0] => string(40) “Имя пользователя должно состоять из двух слов.”
 [1] => string(31) “Ваш логин нам не нравится.”
 [2] => string(25) “email must be valid email”
 [3] => string(5) “Ну и где же ваш пароль?”
 [4] => string(9) “Жаль, что вы не согласны.”
}

Как видите, сообщение для поля электронной почты не применилось, осталось стандартное. А вот сообщение за индексом 4 наоборот! И это при том, что я использовал не название правила, а название поля. В то время как если бы я использовал название правила (trueVal), моё сообщение бы куда-то затерялось. Комментарии опытных пользователей данной библиотеки очень приветствуются.


Sirius Validation


Ок, давайте перейдем к следующей библиотеке и посмотрим, как она справится со схожими задачами.


Определяем правила


И снова нам необходимо определить правило для имени пользователя. Мы его напишем как-то так:


class UserNameRule extends AbstractRule
{
    // Сообщения об ошибках
    const MESSAGE = 'Имя пользователя должно состоять из двух слов.';
    const LABELED_MESSAGE = '{label} должно состоять из двух слов.';

    public function validate($value, $valueIdentifier = null)
    {
        return (bool) preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $value);
    }
}

Обратите внимание на разницу в подходах в сравнении с уже рассмотренными библиотеками. Мы определяем два вида сообщений в константах, нежели используя свойства, методы или аргументы правила.


Теперь давайте опишем логику валидации:


$validator = new Validator;

$validator
    ->add('name', 'required | MyApp\Validation\Rule\UserNameRule')
    ->add('login', 'required | alphanumhyphen', null, 'Логин может содержать только латинские буквы, черточки и подчеркивания.')
    ->add('email', 'required | email', null, 'Пожалуйста, укажите корректный e-mail.')
    ->add('password', 'required | maxlength(64)', null, 'Ваш пароль, сударь.')
    ->add('agree', 'required | equal(true)', null, 'Почему же вы не согласились?');

Как видите, набор правил весьма прост и читабелен. Для описания мы используем названия, разделенные горизонтальными черточками. Этот подход похож на тот, что используется в Laravel и Kontrolio.


Четвертый аргумент метода add() описывает сообщение об ошибке валидации, которое Sirius использует, если валидация будет провалена. А почему же мы не добавили сообщение для нашего нового правила UserNameRule?


$validator->add('name', 'required | MyApp\Validation\Rule\UserNameRule')

Так это потому, что сообщения уже описаны в константах класса:


class UserNameRule extends AbstractRule
{
    // Сообщения об ошибках
    const MESSAGE = 'Имя пользователя должно состоять из двух слов.';
...

Другой вариант — использовать метод addMessage() самого валидатора:


$validator->addMessage('email', 'Пожалуйста, укажите корректный e-mail.');

Обратите внимание, что кастомные правила идентифицируются по полному названию их класса, в то время как в Kontrolio можно задать псевдоним/алиас.


Обратная связь


Чтобы выполнить валидацию, мы вызываем метод валидатора validate(), передавая в него данные:


$data = [
    'name'  => 'Альберт', // Должно быть два слова
    'login' => '@lbert', // "Запрещенный" символ @
    'email' => 'что-то не то', // Здесь должен быть e-mail
    'password' => '' // Пароль вообще не указан
    // 'agreed' нет в массиве, потому что пользователь не установил флажок
];

$validator->validate($data);

В отличие от Respect, Sirius не выкинет исключение, а просто вернет false. Сообщения об ошибках валидации можно получить через метод валидатора getMessages(). Он возвращает ошибки, сгруппированные по атрибутам, так что для прохода по ошибкам нам понадобится два цикла foreach:


foreach ($validator->getMessages() as $attribute => $messages) {
    foreach ($messages as $message) {
        echo $message->getTemplate() . "\n";
    }
}

Здесь $message — объект класса Sirius\Validation\ErrorMessage, у которого есть метод getTemplate(), возвращающий то самое необходимое нам сообщение.


Valitron


Поехали дальше. Еще одно интересное решение — Valitron. Valitron отличается от остальных реализацией добавления и описания правил валидации.


Определяем правила


Первое отличие: чтобы добавить новое правило, не нужно создавать отдельный класс. Можно просто использовать замыкание, возвращающее булевский результат.


Для добавления кастомных правил в Valitron есть статический метод addRule(), в котором первые два аргумента обязательны, а третий — по желанию. Мне понравился такой способ, так как тут сразу в одном месте указывается идентификатор правила, логика и сообщение об ошибке.


use Valitron\Validator;

Validator::addRule('two_words', function($field, $value) {
    return (bool) preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $value);
}, 'Имя пользователя должно состоять ровно из двух слов.');

Второе отличие — то, как правила применяются к атрибутам. Во всех предыдущих случаях мы видели, что атрибут — вещь как бы первичная.


В Valitron пошли другим путем и на первое место поставили именно правила валидации. Описывая правила, вы как бы применяете атрибуты к этим правилам, а не наоборот.


$validator = new Validator($data);
$validator
    ->rule('two_words', 'name')->label('')
    ->rule('required', [
        'name', 'login', 'email', 'password', 'agreed'
    ])
    ->rule('slug', 'login')
    ->rule('email', 'email')
    ->rule('accepted', 'agreed');

Как видно из примера, в методе rule() мы сначала пишем название правила, а уже затем указываем атрибуты, которые должны соответствовать этому правилу. Более явный пример — правило required, где показано, как атрибуты «принадлежат» этому правилу.


Valitron (как и другие решения, которые мы успели рассмотреть) предоставляет стандартные сообщения об ошибках. Если вы просто воспользуетесь ими, то увидите, что каждое сообщение начинается с названия соответствующего атрибута.


Valitron подставляет имена атрибутов в текст сообщения даже в том случае, когда используются нестандартные сообщения об ошибках. Поэтому мы и воспользовались методом label() с пустой строкой, чтобы убрать имя атрибута.


$validator->rule('two_words', 'name')->label('')

Обратная связь


Конкретно что касается валидации, API библиотеки Valitron практически ничем не отличается от того, что мы уже видели в статье. Чтобы выполнить валидацию мы вызываем метод валидатора validate():


$validator->validate();

Сообщения об ошибках валидации можно получить с помощью метода getErrors():


$validator->errors();

Сообщения здесь группирутся по атрибутам точно так же, как и в Sirius Validation, за исключеним того, что для сообщения нет отдельного класса, и мы получаем обычный многомерный массив.


foreach ($validator->errors() as $attribute => $messages) {
    foreach ($messages as $message) {
        echo $message . "\n";
    }
}

Kontrolio


Ну и наконец, последняя библиотека на сегодня — моя собственная разработка под названием Kontrolio.


Определяем правила


Снова, в пятый раз мы создадим правило валидации для имени пользователя. Всё относительно просто и стандартно:


namespace MyProject\Validation\Rules;

use Kontrolio\Rules\AbstractRule;

class TwoWords extends Kontrolio\Rules\AbstractRule
{
    public function isValid($input = null)
    {
        return (bool) preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $input);
    }
}

Теперь мы создаем фабрику и регистриуем правило в ней, используя метод extend():


namespace MyProject;

use Kontrolio\Factory;
use MyProject\Validation\Rules\TwoWords;

$factory = Kontrolio\Factory::getInstance()->extend([TwoWords::class]);

После регистрации правила мы можем воспользоваться им в том числе по имени — two_words. Давайте создадим валидатор:


$data = [
    'name'  => 'Альберт', // Должно быть два слова
    'login' => '@lbert', // "Запрещенный" символ @
    'email' => 'что-то не то', // Здесь должен быть e-mail
    'password' => '' // Пароль вообще не указан
    // 'agreed' нет в массиве, потому что пользователь не установил флажок
];

$rules = [
    'name' => 'two_words',
    'login' => 'sometimes|alphadash',
    'email' => 'email',
    'password' => 'length:1,64',
    'agreed' => 'accepted'
];

$messages = [
    'name' => 'Имя пользователя должно состоять из двух слов.',
    'login' => 'Ваш логин нам не нравится.',
    'email' => 'Вы явно не хотите давать нам свой e-mail.',
    'password' => 'Ну и где же ваш пароль?',
    'agreed' => 'Жаль, что вы не согласны.'
];

$validator = $factory->make($data, $rules, $messages);

Мы описали правила, используя синтаксис, схожий с тем, что используется в Laravel, хотя могли использовать и более многословный вариант:


$rules = [
    'name' => new TwoWords,
    'login' => [new Sometimes, new Alphadash],
    'email' => new Email,
    'password' => new Length(1, 64),
    'agreed' => new Accepted
];

Обратная связь


Валидация запускается всё тем же методом validate():


$validator->validate();

Теперь мы можем получить сообщения об ошибках, используя один из методов getErrors() или getErrorsList(). Первый метод позволяет сделать более сложный вывод ошибок, тогда как второй возвращает плоский массив. Используя getErrors() мы можем вывести сообщения как-то так:


<ul class="errors">
    <?php foreach ($errors as $attribute => $messages): ?>
    <li class="errors__attribute">
        <b><?= $attribute; ?></b>
        <ul>
            <?php foreach ($messages as $message): ?>
            <li><?= $message; ?></li>
            <?php endforeach; ?>
        </ul>
    </li>
<?php endforeach; ?>
</ul>

А с getErrorsList() можно сделать более простой список сообщений:


<?php $errors = $validator->getErrorsList(); ?>

<ul class="errors">
    <?php foreach($errors as $error): ?>
        <li class="errors__error"><?= $error; ?></li>
    <?php endforeach; ?>
</ul>

Итог


В данной статье я показал примеры использования следующих библиотек:


  1. Aura.Filter
  2. Respect Validation
  3. Sirius Validation
  4. Valitron
  5. Kontrolio

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


Каждая из библиотек предлагает свои фишки, имеет свои тёмные стороны, так что я думаю, что это дело вкуса и задачи — выбрать ту самую.


Благодарю за прочтение. Сделайте правильный выбор.

Поделиться с друзьями
-->

Комментарии (31)


  1. webmasterx
    24.08.2016 08:48
    +3

    И опять нет ответа на вопроса — какие существенные отличия вашего велосипеда от представленных ранее?


  1. index0h
    24.08.2016 10:45
    +1

    @franzose Посмотрел вашу библиотеку, есть несколько моментов:


    • вы не проверяете вводимые настройки для валидации так же не проверяете, а доступен ли валидатор для конкретно этого типа. Пример для Length.php
    • ваш валидатор подходит только для проверки пользовательского ввода. Это не то, что бы недостаток, а скорее ограничение применения. В случае, если проверять придется много — ваш валидатор станет довольно прожорливой штукой так как на каждую проверку создается тьма объектов.
    • По своему опыту скажу: задание правил проверки в стиле "not_empty|length:5,15" для крупных проектов — скорее минус, чем плюс. Так как корректность этих правил вы можете узнать на момент запуска, а не на момент написания.

    Посмотрите на досуге: ko-ko-ko/assert


    1. maximkou
      24.08.2016 14:13

      правила в стиле not_empty|length:5,15

      Дополню список проблем:
      1. Если передать в качестве аргумента строку с запятой, все сломается (говорю исходя из кода).
      2. Нет возможности передать массив, да даже если его и передавать, например как JSON, будут опять же проблемы из п.1 с запятыми


      Т.е. в библиотеке такое определение работает «иногда».


  1. epoleacov
    24.08.2016 10:45

    Сделайте правильный выбор

    Выбираю Zend\Validator :)


    1. franzose
      24.08.2016 10:53

      Ну, так как пишу на Laravel чаще всего, то тоже выбрал бы стандартный валидатор. Но если бы пришлось писать на чем-то другом или собирать проект из отдельных пакетов, то тут бы я подумал)


      1. zelenin
        24.08.2016 16:05

        имхо, для проекта надо добавить свой интерфейс Validator (плюс ValidatorResponse). Для каждого кейса валидации следует реализовать конкретный валидатор — UserValidator, CreatePostValidator итд, а вот внутри будет реализация на любой библиотеке из представленных в посте и не представленных.
        Итого гибкость, заменяемость, контрактное программирование итд.

        UPD: причем в таких «узких» валидаторах необязательно пользоваться либами. Можно простые проверки делать базовыми функциями типа is_int, а сложные (проверка емейла с тысячью доменов первого уровня) — с использованием специализированных валидаторов.


        1. epoleacov
          24.08.2016 18:01

          Так и делаем, для этого в Zend есть «формы» как агрегаторы валидаторов и фильтров.


  1. Voenniy
    24.08.2016 10:47
    +2

    Спасибо большое, очень интересный обзор библиотек.

    Немного усложню кейс.
    Допустим поле email необязательное.
    А вот если ставим галочку «подписаться на новости» — то теперь поле email становится обязательным.
    Какая из библиотек умеет работать с такой логикой?

    В Yii это делается через сценарии. Т.е. вначале валидации надо указать сценарий (с подпиской, или без подписки), а в самих правилах указать как действовать в том или ином сценарии.


    1. DmitryKm
      24.08.2016 12:30

      Не уверен, что это лучшее решение, но можно сделать форму где email не обязательное и вторую форму которая наследует первую и только переписывает правило для email. и когда человек сабмитит форму — в зависимости от логики — ты можешь создать нужную тебе форму и провалидировать ее.


    1. sefus
      24.08.2016 12:36
      +1

      Можно же добавить свое правило валидации для поля email и в нем проверять стоит ли галочка.


      1. DmitryKm
        24.08.2016 12:39

        Это если у тебя есть доступ к заполняемой форме внутри кастом правила :) Иногда правило — достаточно «изолирован» и не знает ничего кроме самого себя…


        1. franzose
          24.08.2016 12:40

          Можно переопределить конструктор и передать значение в него. Немного костыльно, но как вариант.


          1. DmitryKm
            24.08.2016 12:42

            Тут надо смотреть как инициализируются правила. Если ты сам их инициализируешь и пихаешь в валидатор — то да, а если допустим ты просто передаешь кастомные рулсы в каком конфиге и они уже разворачиваются внутри валидатора — то тут уже не сработает.


      1. Voenniy
        24.08.2016 12:43

        Переносить логику формы в правила валидации?

        А если так:
        1) Физическое или Юридическое
        2) Если юридическое — то нужны новые обязательные поля (ИНН, Юр адрес, и ещё 100500 полей)
        И получается чо надо создать множество правил, в которых надо проверить галочку. Которую, кстати, тоже надо проверить.


        1. sefus
          24.08.2016 13:26

          Так форма ничего и не должна знать о логике. Логикой пусть занимается модель, а задача валидаторов не пустить в неё некорректные наборы данных из формы.


    1. franzose
      24.08.2016 12:39

      Присоединяюсь к sefus. Любой из представленных валидаторов поддерживает кастомные правила, так что можно реализовать через них. А вот на вопрос, есть ли в них готовые правила по типу required_with точно сейчас не смогу ответить. По крайней мере в Kontrolio такого правила нет.


    1. maximkou
      24.08.2016 14:17

      Такое, насколько я знаю умеют Nette Forms.


    1. Yeah
      24.08.2016 14:18

      Чуть ниже уже писали, что это уже бизнес-логика и такое должна валидировать модель. Форма должна валидировать только формат, а модель — сущности. То есть в вашем случае валидатор формы должен проверить: поле email либо пустое, либо проходит через валидацию формата email. А модель проверяет, что поле email заполнено при проставленном флаге.


      1. symbix
        25.08.2016 11:00

        Не все так просто. Галочка «подписаться на новости» — это такой элемент интерфейса, а на самом деле означает действие «подписка на новости», в коде получится что-то вроде:

        $user = new User(...);
        if ($request->get('subscribe_to_news')) {
        $subscriber->subscribeToNews($user); // может бросить исключение UserEmailRequired
        }

        И тут уж проще добавить правило в валидацию формы, чем ловить исключение и вручную формировать ответ.


    1. Standfest
      29.08.2016 13:31

      Laravel валидатор имеет широкий функционал для такого типа проверок:

      required_if:anotherfield,value,…
      required_unless:anotherfield,value,…
      required_with:foo,bar,…
      required_with_all:foo,bar,…
      required_without:foo,bar,…
      required_without_all:foo,bar,…


  1. DmitryKm
    24.08.2016 12:17
    +1

    Учитывая, что 90% моих проектов на Zend/Symfony, использую Zend\Validator, Symfony\Validator соответственно, особенно нравится Symfony вариант. Больше всего в их решениях привлекает тот факт, что валидация выглядит не отдельной библиотекой которую ты используешь, а интегрированным модулем который ты можешь использовать где угодно (валидация Entity, валидация форм и так далее).


    1. franzose
      24.08.2016 12:41

      Такие standalone-библиотеки, как мне кажется, в основном рассчитаны на тех, кто собирает какие-то свои решения из набора компонентов, а не использует готовые фреймворки. И оба подхода имеют право на существование)


      1. DmitryKm
        24.08.2016 12:44

        Полностью согласен, но опять же Symfony отлично славится своими компонентами :) Берешь Symfony Forms и используешь. Но естественно, должна быть граница и здравый смысл в выборе между использованием легкой библиотеки валидации и огромного компонента для форм от симфонии…


  1. OnYourLips
    24.08.2016 13:39
    +1

    Если я пишу на PHP правила валидации, то я предпочту прописывать ограничения в PHP-коде, а не в строке. Половина валидаторов этим грешат.

    Более того, если мне надо будет использовать валидатор в проекте без фреймворка, то я просто добавлю symfony/validator компонент. Который, между прочим, тут даже не рассматривался.


  1. jigpuzzled
    24.08.2016 17:48

    А еще не забываем о PHPixie Validate, который в отличии от других библиотек работающих только с одномерными массивами умеет валидировать вложенные структуры типа:

    $data = array(
        'name' => 'Pixie',
        'home' => array(
            'location' => 'forest',
            'name'     => 'Oak'
        ),
        'spells' => array(
            'charm' => array(
                'name' => 'Charm Person',
                'type' => 'illusion'
            ),
            'blast' => array(
                'name' => 'Fire Blast',
                'type' => 'evocation'
            ),
            // ....
        )
    );
    


    Что фактически необходимо при работе со всякими ангулярами и MongoDB


    1. franzose
      25.08.2016 01:21

      Если не ошибаюсь, все рассмотренные библиотеки это также умеют. Наверно, надо было включить такой пример в статью.


  1. Danik-ik
    24.08.2016 19:17

    «Ваши имя и фамилия должны состоять ровно из двух слов» — по слогам прочитал Гасан Абдурахман ибн Хоттаб, после чего вздохнул, пожал плечами, завернул золотишко в платок и отправился на сайт конкурентов.


    1. franzose
      25.08.2016 01:09

      Согласен. Но суть была показать, как в тех или иных библиотеках обрабатываются не совсем стандартные случаи)


  1. arodygin
    25.08.2016 01:02

    Нужно мне было валидировать XHR-запрос, прилетающий в бэкэнд от JS-плагина DataTables. Сам запрос достаточно «развесистый» в структурном плане. Описание тут — https://www.datatables.net/manual/server-side. Обычно использую Symfony Validator, и в этом случае поступил аналогично — https://github.com/arodygin/DataTablesBundle/blob/master/src/Parameters.php.

    Вопрос: Kontrolio так умеет? В частности:

    • проверку «вложенных» структур и массивов,
    • запрет/разрешение лишних атрибутов,
    • запрет/разрешение отсутствия атрибута.

    Ни в коем случае не нападаю, просто интересуюсь, а тратить время на эксперименты с вашей библиотекой не хочется. Если есть — отлично. Если нет — имейте в виду. ;)


    1. franzose
      25.08.2016 01:27

      Вложенные массивы Kontrolio валидировать умеет. Возможно, не так красиво по сравнению с другими решениями. Можно записать так:


      $rules = ['attr.nested.nested.nested' => 'not_empty'];
      $messages = ['attr.nested.nested.nested' => 'Не может быть пустым.'];

      Отсутствие атрибута можно разрешить правилом Sometimes:


      $rules = ['attr.nested.nested.nested' => [new Sometimes, new Length(5, 15)];

      А вот «лишние» атрибуты не умеет отфильтровывать. Грубо говоря, что в библиотеку передали, то она и будет валидировать :)


      1. arodygin
        25.08.2016 01:40

        Хорошо, спасибо!