Валидация сложных структур с PHPixie Validate

от автора

image
Сегодня вышел еще один компонент PHPixie 3, в этот раз для валидации данных. Библиотек для PHP которые занимаются валидацией уже достаточно, зачем тогда писать еще один? На самом деле у большинства из них есть большой недостаток — они работают только с одномерными массивами данных ориентируясь в первую очередь на работу с формами. Такой подход неизбежно устарел в мире API и REST, все чаще приходиться работать с документообразными запросами со сложной структурой. Validate с самого начала был спроектирован как раз чтобы справляться с такими задачами. И даже если вы не используете PHPixie этот компонент может вам очень пригодиться.

Начнем с простого примера, простого одномерного массива:

// Собственно сами данные  $data = array(     'name' => 'Pixie',     'home' => 'Oak',     'age'  => 200,     'type' => 'fairy' );  $validate = new \PHPixie\Validate();  // Создаем валидатор $validator = $validate->validator();  // По сути одномерный массив это простой документ $document = $validator->rule()->addDocument();  // Для задания самых правил поддерживаются несколько // вариантов синтаксиса. Сначала попробуем стандартный  // Обязательное поле с фильтрами $document->valueField('name')     ->required()     ->addFilter()         ->alpha()         ->minLength(3);  // Фильтры также можно задать массивом $document->valueField('home')     ->required()     ->addFilter()         ->filters(array(             'alpha',             'minLength' => array(3)         ));  // Или в случае одного фильтра // просто передать его сразу $document->valueField('age')     ->required()     ->filter('numeric');  // свой колбек для конкретного поля $document->valueField('type')     ->required()     ->callback(function($result, $value) {         if(!in_array($value, array('fairy', 'pixie'))) {             // Задаем свою ошибку             $result->addMessageError("Type can be either 'fairy' or 'pixie'");         }     });  // По умолчанию валидатор не пропустит поля // для которых нет правил валидации. // Но эту проверку можно отключить $document->allowExtraFields();  // свой колбек для всего документа $validator->rule()->callback(function($result, $value) {     if($value['type'] === 'fairy' && $value['home'] !== 'Oak') {         $result->addMessageError("Fairies live only inside oaks");     } }); 
То же самое но с альтернативным синтаксисом

$validator = $validate->validator(function($value) {     $value->document(function($document) {         $document             ->allowExtraFields()             ->field('name', function($name) {                 $name                     ->required()                     ->filter(function($filter) {                         $filter                             ->alpha()                             ->minLength(3);                     });             })             ->field('home', function($home) {                 $home                     ->required()                     ->filter(array(                         'alpha',                         'minLength' => array(3)                     ));             })             ->field('age', function($age) {                 $age                     ->required()                     ->filter('numeric');             })             ->field('type', function($home) {                 $home                     ->required()                     ->callback(function($result, $value) {                         if(!in_array($value, array('fairy', 'pixie'))) {                             $result->addMessageError("Type can be either 'fairy' or 'pixie'");                         }                     });             });     })     ->callback(function($result, $value) {         if($value['type'] === 'fairy' && $value['home'] !== 'Oak') {             $result->addMessageError("Fairies live only inside oaks");         }     }); }); 

И сама валидация:

$result = $validator->validate($data); var_dump($result->isValid());  // Добавим немного ошыбок $data['name'] = 'Pi'; $data['home'] = 'Maple'; $result = $validator->validate($data); var_dump($result->isValid());  // Выведем ошибки foreach($result->errors() as $error) {     echo $error."\n"; } foreach($result->invalidFields() as $fieldResult) {     echo $fieldResult->path().":\n";     foreach($fieldResult->errors() as $error) {         echo $error."\n";     } }  /* bool(true) bool(false) Fairies live only inside oaks name: Value did not pass filter 'minLength' */ 

Работа с результатами

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

if($error->type() === 'filter') {     if($error->filter() === 'minLength') {        $params = $error->parameters();        echo "Please enter at least {$params[0]} characters";     } } 

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

Структуры данных

Ну вот собственно «киллер фича», попробуем провалидировать вот такую структуру:

$data = array(     'name' => 'Pixie',          // 'home' это просто субдокумент     'home' => array(         'location' => 'forest',         'name'     => 'Oak'     ),          // 'spells' массив субдокументов одного типа,     // и текстовым ключом (его тоже надо проверить)     // of the same type     'spells' => array(         'charm' => array(             'name' => 'Charm Person',             'type' => 'illusion'         ),         'blast' => array(             'name' => 'Fire Blast',             'type' => 'evocation'         ),         // ....     ) );  $validator = $validate->validator(); $document = $validator->rule()->addDocument();  $document->valueField('name')     ->required()     ->addFilter()         ->alpha()         ->minLength(3);  // Субдокумент $homeDocument = $document->valueField('home')     ->required()     ->addDocument();  $homeDocument->valueField('location')     ->required()     ->addFilter()         ->in(array('forest', 'meadow'));  $homeDocument->valueField('name')     ->required()     ->addFilter()         ->alpha();  // Массив субдокументов $spellsArray = $document->valueField('spells')     ->required()     ->addArrayOf()     ->minCount(1);  // Правила для ключа $spellDocument = $spellsArray     ->valueKey()     ->filter('alpha');  // Правила для элемента массива         $spellDocument = $spellsArray     ->valueItem()     ->addDocument();  $spellDocument->valueField('name')     ->required()     ->addFilter()         ->minLength(3);  $spellDocument->valueField('type')     ->required()     ->addFilter()         ->alpha(); 
То же самое используя альтернативный синтаксис

$validator = $validate->validator(function($value) {     $value->document(function($document) {         $document             ->field('name', function($name) {                 $name                     ->required()                     ->filter(array(                         'alpha',                         'minLength' => array(3)                     ));             })             ->field('home', function($home) {                 $home                     ->required()                     ->document(function($home) {                                                  $home->field('location', function($location) {                             $location                                 ->required()                                 ->addFilter()                                     ->in(array('forest', 'meadow'));                             });                                                  $home->field('name', function($name) {                             $name                                 ->required()                                 ->filter('alpha');                         });                     });             })             ->field('spells', function($spells) {                 $spells->required()->arrayOf(function($spells){                     $spells                         ->minCount(1)                         ->key(function($key) {                             $key->filter('alpha');                         })                         ->item(function($spell) {                             $spell->required()->document(function($spell) {                                 $spell->field('name', function($name) {                                     $name                                         ->required()                                         ->addFilter()                                             ->minLength(3);                                 });                                                                      $spell->field('type', function($type) {                                     $type                                         ->required()                                         ->filter('alpha');                                 });                             });                     });                 });             });     }); }); 

Альтернативний синтаксис на мой взгляд гораздо читабельнее в таком случае, так как табуляция кода совпадает с табуляцией документа.

Посмотрим на результаты

$result = $validator->validate($data);  var_dump($result->isValid()); //bool(true)  // Добавим ошибок $data['name'] = ''; $data['spells']['charm']['name'] = '1';  // Невалидный чисельный ключ $data['spells'][3] = $data['spells']['blast'];  $result = $validator->validate($data);  var_dump($result->isValid()); //bool(false)  // рекурсивная функция для вывода ошыбок function printErrors($result) {     foreach($result->errors() as $error) {         echo $result->path().': '.$error."\n";     }          foreach($result->invalidFields() as $result) {         printErrors($result);     } } printErrors($result);  /* name: Value is empty spells.charm.name: Value did not pass filter 'minLength' spells.3: Value did not pass filter 'alpha' */ 

Демо

Чтобы попробовать Validate своими руками достаточно:

git clone https://github.com/phpixie/validate cd validate/examples   #если у вас еще нет Композера curl -sS https://getcomposer.org/installer | php   php composer.phar install php simple.php php document.php 

И кстати как и у всех других библиотеках от PHPixie вас ждет 100% покрытие кода тестами и работа под любой версией PHP старше 5.3 (включая новую 7 и HHVM).

ссылка на оригинал статьи http://habrahabr.ru/post/269709/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *