Skip to content

Ru:formarticle

soloweb edited this page Apr 24, 2012 · 5 revisions

Форма (статья)

Вступление

Мы часто работаем с различными данными - данные пользователей, данные других систем, данные парсеров, данные ботов. Хорошая практика - не доверять входящим данным и валидировать их. В 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 protected]', 'yearOld' => '20'), то массив $errorsList будет пустым. В таком случае можно получить 'чистые' данные:

<?php
$email = $form->getValue('email');
$yearOld = $form->getValue('yearOld');

Различные вызовы импорта

Бывает необходимость импортировать в форму данные из нескольких источников. В таких случаях можно использовать метод importMore:

<?php
$form
	->import(array('email' => '[email protected]')
	->importMore(array('yearOld' => '20');

Отличие import от importMore - в случае первого (import) перед импортированием значения всех примитивов формы очищаются.

Так же иногда возникает необходимость из массива входящих данных импортировать значение лишь для какого-то одного примитива, для этого используются методы Form'ы importOne и importOneMore:

<?php
$form->importOne('email', array('email' => '[email protected]', '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";
}

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

FormUtils и Proto

Для любого объекта имеющего 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);
}

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