-
Notifications
You must be signed in to change notification settings - Fork 52
Ru:formarticle
Мы часто работаем с различными данными - данные пользователей, данные других систем, данные парсеров, данные ботов. Хорошая практика - не доверять входящим данным и валидировать их. В onPHP для этого используются Form'ы и Primitive'ы.
Form - класс контейнер, хранящий совокупность примитивов и позволяющий делать с ними групповой импорт и экспорт.
Операция импорта в примитиве - получение из строковых значений производных, валидных значений в Primitiv'ах.
Операция экспорта в примитиве обратна операции импорта - имея заполненый примитив - получение из него соответствующего строкового значения.
Если для импорта в примитив передается одно конкретное значение, то в форму для импорта необходимо передавать ассоциативный массив вида array(primitiveName => stringvalue, ...)
Производные значения от строковых - это те значения, которые примитивы возвращают через метод getValue, например, для PrimitiveString - строка, для PrimitiveEnumeration - Enumeration объект, для PrimitiveBoolean - true|false и т.д.
Пусть у нас есть форма в html в которой пользователь должен передать нам свой email (обязателен) и возраст (необязателен). Тогда создадим форму с двумя примитивами:
<?php
$form = Form::create()->
add(Primitive::string('email')->setAllowedPattern(PrimitiveString::MAIL_PATTERN)->required())->
add(Primitive::integer('yearOld')->setMin(1));
Теперь им:
<?php
$form->import(array('email' => '123', 'yearOld' => 'twenty');
Дальше нужно проверить форму на наличие ошибок:
<?php
$errorsList = $form->getErrors();
В данном случае в импорт были переданы не валидные данные и $errorsList оказался не пустым: array('email' => 1, 'yearOld' => 1)
. Если импортировать правильные значения, напримери array('email' => 'email@example.com', 'yearOld' => '20')
, то массив $errorsList будет пустым. В таком случае можно получить 'чистые' данные:
<?php
$email = $form->getValue('email');
$yearOld = $form->getValue('yearOld');
Бывает необходимость импортировать в форму данные из нескольких источников. В таких случаях можно использовать метод importMore:
<?php
$form
->import(array('email' => 'email@example.com')
->importMore(array('yearOld' => '20');
Отличие import от importMore - в случае первого (import) перед импортированием значения всех примитивов формы очищаются.
Так же иногда возникает необходимость из массива входящих данных импортировать значение лишь для какого-то одного примитива, для этого используются методы Form'ы importOne и importOneMore:
<?php
$form->importOne('email', array('email' => 'email@example.com', 'yearOld' => '20'));
В данном случае будет импортирован только email.
Особняком стоит метод importValue. В отличие от других импортов в него нужно передавать объект такого же вида, который можно получить при вызове метода getValue (т.е. производный объект от строки для данного примитива).
Это последовательный вызов у всех примитивов метод export и составление ассоциативного массива вида array(primitiveName => exportedValue)
. Т.е. результат экспорта это такой массив, который можно импортировать куда-то еще.
В некоторых случаях часть примитивов удобно связать между собой простыми правилами. Для правил у формы есть метод addRule, где первый аргумент имя правила, а второй логическое выражение. Правила можно использовать, например, в форме где пользователь должен ввести пароль и повторить его:
<?php
$form = Form::create()->
add(Primitive::string('username')->required())->
add(Primitive::string('password')->required())->
add(Primitive::string('passwordRepeat')->required())->
addRule(
'ruleName',
Expression::eq(
FormField::create('password'),
FormField::create('passwordRepeat')
)
);
Надо отметить, что проверка правил не происходит при каждом импорте и вызывать проверку нужно методом $form->checkRules();
Узнать, что правило не было выполнено можно через метод $form->getErrors()
.
Так же ошибки правил не сбрасываются при каждом импорте или при каждом вызове checkRules. Так что, если вы используете одну и ту же форму с правилами для нескольких импортов не забудьте перед очередным импортом вызвать у формы метод $form->dropAllErrors()
Использовать правила или писать руками дополнительную валидацию - каждый решает сам в зависимости от задачи. Где-то может быть удобно одно, а где-то другое. Пример дополнительной валидации вместо описанного правила будет чуть ниже.
У формы по умолчанию для любого примитива-правила есть два 'своих' типа ошибок:
- Form::WRONG = 1 - во входящих параметрах значение для импорта было, но оно не прошло валидацию
- Form::MISSING = 2 - у примитива было установлено свойство required и при импорте значение не было импортировано Для любого примитива или правила в форме указать, что он с ошибкой. Например, возьмем предыдущую форму:
<?php
$form = Form::create()->
add(Primitive::string('username')->required())->
add(Primitive::string('password')->required())->
add(Primitive::string('passwordRepeat')->required())->
import($dataForImport);
$password = $form->getValue('password');
$passwordRepeat = $form->getValue('passwordRepeat');
if ($password && $passwordRepeat && $password != $passwordRepeat) {
$form->markWrong('passwordRepeat');
}
Таким образом если пароли не совпадают, то получим следующую ошибку getErrors: array('passwordRepeat' => 1)
Так же можно установить тип ошибки, не 1, а какой-то другой через метод формы $form->markCustom('primitiveName', $customErrorCode)
. Если ошибка компенсирована чем-то другим или в каких-то еще случаях ее надо снять - для этого можно воспользоваться методом $form->markGood('primitiveName')
В форме для всех ошибок и правил можно добавлять текстовые сообщения, которые затем можно показать пользователю. За добавление ошибок отвечают методы addWrongLabel('primitiveName', $textMsg)
, addMissingLabel('primitiveName', $textMsg)
и addCustomLabel('primitiveName', $customErrorCode, $textMsg)
. Получить текст ошибки для примитива можно через метод getTextualErrorFor('primitiveName'). Пример работы с ошибками:
<?php
$form = Form::create()->
add(Primitive::integer('ageOld')->required())->
addWrongLabel('ageOld', 'Неправильное значение возраста')->
addMissingLabel('ageOld', 'Поле возраста обязательно для заполнения')->
addCustomLabel('ageOld', 3, 'Возраст не может быть отрицательным')->
addCustomLabel('ageOld', 4, 'Возраст не может быть больше 99')->
import($data);
$age = $form->getValue('ageOld');
if ($age < 0) {
$form->markCustom('ageOld', 3);
} elseif ($age > 99) {
$form->markCustom('ageOld', 4);
}
if ($form->getErrors()) {
print "ageOld field error msg: ".$form->getTextualErrorFor('ageOld');
} else {
print "Value imported success";
}
Это несколько утрированный пример, т.к. минимальное и максимальное значение можно задать прямо в примитиве, но так достаточно просто иллюстрируется как использовать ошибки.
Для любого объекта имеющего proto можно получить автоматическую форму:
<?php
MyBussinessObject::proto()->makeForm();
В некоторых случаях этот метод стоит переопределить и прежде чем отдать форму немножко ее подредактировать, например, добавив в PrimitiveString'и какие-либо фильтры.
В onPHP есть статический класс FormUtils, обладающий двумя основными методами:
- object2form - заполняет собранную по объекту форму данными из объекта
- form2object - заполняет объект данными из формы
Все это можно использовать для простого/легкого редактирования объектов:
<?php
$form = MyBussinessObject::proto()->makeForm();
$form->import($someData);
if (!$form->getErrors()) {
$objectPrototype = MyBussinessObject::create();
FormUtils::form2object($form, $objectPrototype);
$objectPrototype = $objectPrototype->dao()->take($objectPrototype);
print "object created/saved success";
} else {
print "object data validation errors: ".print_r($form->getErrors(), true);
}
Безусловно в более сложных примерах тут может понадобиться дополнительная валидация входных параметров, а так же не все значения необходимо будет перезаписывать, но основа примерно такая.