Валидация формы на фреймворке Kohana, класс Validation. Часть 2
В первой части я рассказал как построить форму используя фреймворк Kohana, во второй части я хочу рассказать как происходит валидация формы и вывод ошибок.
Как обычно, для просмотра примера используем форму представленную тут.
Статья написано для работы с версией Kohana 3.3.0 (текущая версия на момент написания статьи)
Класс Validation (валидация формы)
Для чего вообще требуется валидировать данные? Есть много причин — защита от дурака, получения более правдивых данных, предотвращение XSS атак и тд. Лично для меня валидация занимает важное место в создании сайта, т.к. именно с правильными данными приходится потом работать и если не делать проверку, например, на номер телефона (цифры и некоторые символы), эл.почты, ссылки и тд, потом можно очень сильно поплатиться.
Например если не проверить вводимый регистрируемый эл.адрес хотя бы на правильность написания, то в дальнейшем юзеру не будет возможности восстановить пароль и будут куча писем в саппорт… или например, многие создатели сайтов просят ввести номер телефона для контакта, и юзеры вводят местные номера, но блин, этоже инет, откуда я знаю из какого города, страны этот юзер!!! А юзер не ввел код и все, поэтому лучше всего проводить валидацию и указывать юзеру, что он не прав и код города (оператора связи) вводить надо!
Рассмотрим основные методы для валидцаии класса.
Создание экземпляра класса Validation
Есть два способа, классический
$post = Security::xss_clean($this->request->post()); // данные массива $_POST обработанные от XSS $valid = new Validation(array_merge($post, $_FILES)); //тут я передаю в валидатору сразу два массива данных $_POST и $_FILES
и с помощью паттерна фабрики
$post = Security::xss_clean($this->request->post()); // данные массива $_POST обработанные от XSS $valid = Validation::factory(array_merge($post, $_FILES)); //тут я передаю в валидатору сразу два массива данных $_POST и $_FILES
Правила (rule, rules)
С помощью этих методов передается название поля, которое требуется провалидировать, и правило которое требуется для этого.
Например, необходимо, чтобы поле «Ваше имя» было заполнено, для этого создаем правило
$valid->rule('name', 'not_empty');
Первый параметр метода rule это название поля валидации в теге input формы, второй параметр это метод класса Valid который будет применен к этому полю. В ядре фреймворка в классе Valid. Это самый простой метод валидации, т.к. он не требует дополнительных параметров, просто проверить данные на пустоту и все. Есть более сложные методы проверки, например когда вам нужно проверить данные и для этого требуется передать методу класса Valid дополнительные параметры, например проверить данные на соответствие регулярному выражению, тогда запись правила валидации выглядит немного иначе:
$this->template->form->type_error = array( '0'=>' ', '1'=>'изменение информации о сайте', '2'=>'стилистические, грамматические, орфогр. ошибки' ); ... $valid->rule('type_error', 'regex', array(':value','/[1-2]+/'));
Разберем подробнее. Есть поле формы с именем type_error, это тег типа select в нём имеются 3 опции, так вот важно чтобы были выбраны опции со значениями 1 или 2, поэтому я решил сделать проверку выбора с помощью регулярного выражения. Поэтому вторым параметром я указываю название метода, который есть в классе Valid. Метод требует для работы 2 параметра
public static function regex($value, $expression) { return (bool) preg_match($expression, (string) $value); }
А данные для метода Valid::regex идут в 3-й параметром в методе проверки array(‘:value’,’/[1-2]+/’),
где :value — это текущее значение поля формы type_error,
‘/[1-2]+/’ — это регулярное выражение.
Теперь надеюсь понятно, как строятся правила валидации для полей формы. Это еще не все пояснения, дальше будет.
Бывает случаи, когда для одного поля требуется написать сразу несколько правил валидации. Можно воспользоваться методом $valid->rule(…) и написать их столько к полю, сколько нужно, а можно просто использовать другой метод $valid->rules(…) для добавления сразу нескольких правил валидации за один раз, рассмотрим пример.
В форме есть поле email, это поле обязательно для заполнения, следовательно оно не должно быть пустым, еще нужно проверять,чтобы длина эл. адреса не превышала 254 символов, а еще чтобы туда был введен правильные электронный адрес, следовательно мне нужно использовать 3 правила валидации.
$valid->rules( 'email', array( array('not_empty'), array('max_length', array(':value', 254)), array('email') ) );
Способом выше можно записать сразу все правила валидации для одного поле, обратите внимание, что метод Valid::max_length требует 2 параметра, значение и максимальную длину, которую я и передал.
Это способы которые идут «из коробки», но бывают ситуации, когда нужно проверить данные таким способом, который не описан в классе Valid, например проверить Captcha, на пустоту поля проверить можно, а вот правильность ввода как? Также есть решения:
$valid->rules( 'captcha', array( array('not_empty'), array('Captcha::valid') ) );
Передаем сразу все правила за один раз в валидатор, проверяем на пустоту, стандартным методов Valid::not_empty, а также на корректность ввода, но обработку данных будет уже вести метод Captcha::valid, класс расположен в модуле Captcha. Еще пример такой же записи, теперь для картинок.
$valid->rule('img', 'Upload::type', array(':value', array('jpg', 'png', 'gif')))
Требуется загружать файлы, только с расширениями ‘jpg’, ‘png’, ‘gif’, за эту валидация отвечает метод Upload::type, который требует 2 параметра, данные и типы файла.
Как создать свой собственный валидатор я расскажу в следующей статье, а теперь переходим к другим метода класса валидации — label и labels.
Метки (label, labels)
Метод label нужен для вывода ошибок, а также получения переводов ошибок, об этом чуть позже. Пока расскажу как с ним работать.
Переменная $_labels внутри класса Validation заполняется самостоятельно при создании правил валидации, в дальнейшем по данным массива этих меток находятся переводы для заполнения массива ошибок, об этом тоже чуток позже. Но есть нюансы, в самом месте где происходит заполнение стоит регулярное выражение, которые удаляет «лишние» символы из название полей:
$this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
поэтому если у вас имеется в названии поля -, _ и прочие знаки, вам нужно указать это в методе $valid->label(…). В моём примере также имеется поле с «неправильным названием» type_error и для того, чтобы метка не изменилась, я указываю её:
$valid->label('type_error', 'type_error');
Конечно можно и поступить иначе, ошибки формы и переводы также буду показываться, даже если не указывать метку для «проблемного» поля, но я предпочитаю единообразие в назывании, чтобы через какое-то время не вспоминать, а чего тут поле называется type_error, а в переводах оно «type error«. Метод $valid->labels(…) принимает просто массив меток с параметром ключ=>значение, просто за один метод передать все данные формы.
Вывод ошибок (errors)
При валидации формы необходимо, в случае ошибок валидации, уведомлять об этом пользователя, что в таком-то поле вы что-то не то сделали. Для таких целей существует метод $valid->errors(…), рассмотрим его работу:
public function errors($file = NULL, $translate = TRUE){...}
Методу для работы требуется 2 параметра — название файла, в котором содержатся объяснения ошибок или переводы этих ошибок, второй параметр отвечает за перевод, по-умолчанию перевод включен.
Я назвал свой файл contact, сам файл можно размещать либо в модуле, либо в главной папке (application/messages/), запомните, что если будет находится 2 одинаковых файла в главной папке и в модуле (пример /modules/modulename/messages/), то данные будут прочитаны из главной папки, т.к. у Kohana такая иерархия обхода директорий. Т.к. модуля у меня нет, то я все примеры и переводы храню в главной папке (/application/messages/contact.php), вот его содержание:
<?php defined('SYSPATH') or die('No direct script access.'); $messages = array( 'name' => array( 'not_empty' => ':field must not be empty', ), 'email' => array( 'not_empty' => ':field must not be empty', 'max_lenght' => ':field must not exceed :param2 characters long', 'email' => ':field not email address' ), 'sex' => array( 'not_empty' => ':field must not be empty', ), 'type_error' => array( 'regex' => ':field does not match the required format', ), 'descr' => array( 'not_empty' => ':field must not be empty', ), 'img' => array( 'Upload::not_empty' => ':field must not be empty', 'Upload::type' => ':filed is not allowed file type', ), 'captcha' => array( 'Captcha::valid'=> ':field captcha not valid' ), ); return $messages;
На примере кусочка файла разберем его структуру, для примера возьмем эту часть:
'email' => array( 'not_empty' => ':field must not be empty', 'max_lenght' => ':field must not exceed :param2 characters long', 'email' => ':field not email address' ),
где, email — название поля в форме,
далее массив, в котором имеются ключи — это название метода в классе проверки, в данном случае Valid::not_empty и тд.
Ключ not_empty имеет значение :field must not be empty, что это? В ядре фреймворка имеется изначальный файл переводов (только английский язык), при проверке именно английское название будет выводится, часть начинающаяся с двоеточий :field будет заменена на название проверяемого поля, вот что получится — email must not be empty, вроде бы ура, всё ясно, но это не по-людски, нужна так выводить ошибки, чтобы человек мог понять, что от него просят! Поэтому нужно оформить переводы, т.к. у меня только русская локаль, то перевод я сделаю для неё, по аналогичной схеме можно делать переводы и для других языков. Конечно можно сделать халтуру и просто написать свой перевод вместо значения :field must not be empty, что-то типа, «Заполните электронный адрес!«, но в таком случае на всех языках сайта будут только один перевод на одном языке.
Немного о том, как создавать файл переводов. Файлы переводов хранятся или в главной папке application/i18n или в файле модуля, подчиняется тем же правилам считывания, что и все файлы фреймворка. В папке переводов i18n создается папка с двухзначным буквенным кодом локали в нижнем регистре, по ISO 639-1 (смотрите Вики), далее в этой папке будет файл с переводами, файл также создается по ISO 639-1 в нижнем регистре, например для русского языка будет ru, т.е. получится такая цепочка application/i18n/ru/ru.php или, для Украины, application/i18n/uk/ua.php, для Британии application/i18n/en/gb.php и тд.
Пример файла перевода:
<?php defined('SYSPATH') or die('No direct script access.'); return array( // Переводы для валидации ':field must not be empty' => 'Поле "<span>:field</span>" не должно быть пустым.', ':field not email address' => 'Поле "<span>:field</span>" содержит не корректный e-mail адрес.', ':field captcha not valid' => 'В поле "<span>:field</span>" введён не верный код.', ':filed is not allowed file type' => 'Файл в поле "<span>:field</span>" должен иметь расширения <span>:param2</span>.', ':field does not match the required format' => 'Поле "<span>:field</span>" не выбрано', ':field must not exceed :param2 characters long' => 'Поле "<span>:field</span>" имеет длину больше чем <span>:param2</span> символа.', // Переводы для полей форм 'name' => 'Ваше имя', 'email' => 'Ваш эл. адрес', 'sex' => 'Укажите Ваш пол', 'type_error' => 'Тип проблемы', 'descr' => 'Описание проблемы', 'captcha' => 'Введите код', 'img' => 'Картинка с ошибкой' );
где, ключ — можно найти в файле contact.php, все такие названия будут заменены на значения файла перевода,
ну а значение ключа — это и есть перевод.
В переводах можно заметить некие сущности, такие как :field, :param2 и тд, эти сущности будут подменяться на название поля для :field и значение которое мы передаем в правиле для валидации.
Например, если посмотреть на одно из правил валидации поля эл. адреса, то можно увидеть, что мы передаем в метод валидации некие параметры array(‘max_length’, array(‘:value’, 254)), вот эти параметры можно получить в выводе ошибок, например второй параметр имеет сущность :param2 и будет равен 254, и полный текст ошибки будет такой «Поле «Ваш эл. адрес» имеет длину больше чем 254 символа.»
Ниже в файле видны переводы для полей формы, ключ — название поля в форме, а значение его человеческое обозначение. В результате всех этих мероприятий будет красиво и грамотно оформлены вывод ошибок.
Многие могут спросить, а зачем заполнять массив в файле contact.php? Да, можно просто оставить пустой массив, а использовать только файлы-переводов, но файл contact.php должен быть, иначе получите не информативный массив ошибок, с котором ничего не сделаешь. Если массив файла contact.php будет пуст, то весь перевод будет браться из файлов-переводов и эти ошибки будет ОДИНАКОВЫ для всех форм на сайте, если же вы хотите вывести другое описание ошибки с одинаковым типом ошибки, то вам нужно заполнить массив и значение перевода выдумать своё, а далее это значение написать в файл переводов и для одной из форм у вас будет иной перевод.
Пример:
файл application/messages/contact.php <?php defined('SYSPATH') or die('No direct script access.'); $messages = array( 'sex' => array( 'not_empty' => 'pol_ukagi', ), ); return $messages; файл application/i18n/ru/ru.php <?php defined('SYSPATH') or die('No direct script access.'); return array( // Переводы для валидации 'pol_ukagi' => 'А вы кто, девочка или мальчик?', );
После это вывод поля sex измениться с Поле «Укажите Ваш пол» не должно быть пустым. на А вы кто, девочка или мальчик?.
Теперь у вас все сделано для оформления ошибок, хочу еще сказать как получить массив всех этих ошибок. Как раз метод $valid->errors(‘contact’); и выдаст массив ошибок, где ключ — название поля, а значение отформатированное название ошибки. Далее делайте с ним, что вы хотите, например я во вьюхе пробежался по нему циклом и вывел все ошибки.
Контроллер
Хочу показать оформление контроллера.
public function action_index() { $this->template->title = 'Пример работы валидатора'; $this->template->form = new View('form_contact'); $this->template->form->type_error = array( '0'=>' ', '1'=>'изменение информации о сайте', '2'=>'стилистические, грамматические, орфогр. ошибки' ); $post = $this->request->post(); if (count($post) > 0) { $post = Security::xss_clean($post); $valid = new Validation(array_merge($post, $_FILES)); $valid->rule('name', 'not_empty', array(':value')) ->rules( 'email', array( array('not_empty'), array('max_length', array(':value', 254)), array('email'), ) ) ->rule('sex', 'not_empty') ->rule('type_error', 'regex', array(':value','/[1-2]+/'))->label('type_error', 'type_error') ->rule('descr', 'not_empty') ->rule('img', 'Upload::type', array(':value', array('jpg', 'png', 'gif'))) ->rules( 'captcha', array( array('not_empty'), array('Captcha::valid') ) ) ; if ($valid->check()) { print_r($valid->data()); // валидированные данные print_r($post); // пост данные print_r($_FILES); // данные файлов exit; } else { $this->template->form->errors = $valid->errors('contact'); } } }
Как видно из контроллера, мы получаем переменную post() c данными формы, прогоняем данные через очистку от XSS, далее создаем экземпляр класса валидации, куда передаем смешанный массив данных для валидации, далее оформляем правила валидации, и осуществляем проверку, прошли проверку валидации — вывели данные массивов, а там делайте с ними, что вам угодно, не прошли валидацию — получите список ошибок валидации.
Валидация через ORM
В одной из следующих статей я рассмотрю работы формы и взаимодействие с БД. Этот способ мне нравится больше, чем просто валидация, там тоже есть на что обратить внимание.
Статья просмотренна 659634 раз, зашло посетителей 67818
И так появилось несколько вопросов, если не затруднит ответьте.. =)
1) Что по поводу как подружить валидатор с filter_var?
2) Я предполагаю что подсветка ошибок валидации работает через асинхронку и соответственно хотелось бы рассмотреть саму реализацию =)
3) Было бы не плохо рассмотреть где лежат СSS правила для подсветки ошибок валидации…
Всё коды есть, серверная часть в статье, а юзерская вы сами видите при просмотре HTML и CSS.
Как сделать валидацию multiselect? Делаю валидацию в модели (ORM)
<?=Form::select('cat[]', $cats, $data['cat'], array('multiple' =>'multiple','size'=>5,'id'=>'selectmultiple1','class'=>'span6'))?>
где cat[] — name
$cats — массив, который передается из контроллера (поля option)
$data['cat'] — значение, которое считываеся.
Ну проще будет в правиле написать чтобы валидацию проводил какой-то сторонний класс, а в нём вы уже проведете свою логику и будите отдавать true/false
array(
'name_id' => array(
array('not_empty'),
array('Validation_Rules::not_selected', array(':value')),
),
);
Спасибо за статью, а можно попросить выложить исходные файлы из статьи ?
Ждёмс статью на тему "Валидация через ORM" )
Спасибо!
ДД! Давайте я вышлю вам на мыло, то мыло какое указано в комментарии рабочее?