- Система отдает данные пользователю - например, новости.
- Система получает данные пользователя - скажем, личные данные для магазина.
К сожалению, люди не идеальны, и не всегда указывают нужную информацию. Есть хороший рабочий принцип - не доверять данным от пользователя. Для такого "недоверия" принято использовать валидацию данных.
Итак, начнем. Опишем класс:
class Customer
{
public $firstName;
public $lastName;
public $age;
public $gender;
}
Допустим, что нужно обязательно запомнить lastName, $age должен быть больше 18 и gender нужно указать обязательно, и он может быть либо "male", либо "female". Как это сделать? Попробуем решение в лоб:
class SomeController
{
public function actionBuy()
{
$customer = $this->getCustomer();
if(
!is_empty($customer->lastName)
&& !is_empty($customer->age)
&& $customer->age > 18
&& !is_empty($customer->gender)
&& in_array($customer->gender, array("male", "female"))
)
{
echo "Correct!";
}
else
{
echo "Invalid!";
}
}
}
Отстойно! Что будет, если потребуется где-то еще провалидировать обьект? И как получить список ошибок? Сделаем для начала классический рефакторинг - вынесем этот ужасный огромный if() прямо в модель, которую валидируем:
class Customer
{
...
public function validate()
{
$violations = array();
if(is_empty($this->lastName))
$violations[] = "Last name required";
if(is_empty($this->age)
$violations[] = "Age required";
if(is_empty($this->gender))
$violations[] = "Gender required";
if(!in_array($this->gender, array("male", "female")))
$violations[] = "Invalid gender";
}
}
Теперь уже намного лучше. Как минимум, вся валидация скрыта от контроллера, и мы можем получить список ошибок, при желании даже сделать интернационализацию!
class SomeController
{
public function actionBuy()
{
$customer = $this->getCustomer();
$errors = $customer->validate();
if(count($errors) == 0)
{
echo "Correct!";
}
else
{
echo "Invalid! list on errors:";
foreach($errors as $error) echo _($error);
}
}
}
Но проблемы решены не все. Что делать, если логика валидации усложняется? Что делать, если паттерны проверок дублируются? А как же еще не раздувать код метода валидации?
Приходит понимание, что модель начинает содержать слишком много логики, да еще в некотором роде сервисной - мы же описали, какие поля нужны! Это можно назвать контрактом на использование кода. Для решения таких сложностей, нужно поступить очень просто.
Отказаться от валидации данных в модели. Это должен делать валидатор.
Отлично, вот оно решение. Но правильный программист - ленивый программист, следовательно попробуем поискать готовое решение.
1) Yii framework - очень быстрый, удобный фреймфорк со своим валидатором. Он бы нам помог, когда мы выносили валидацию в саму модель - в принципе так и сделано в этом фреймворке, модель хранит конфигурацию для валидации данных, сама запускает валидатор, сама заботится о получении списка ошибок и т.п. Все делает сама. К сожалению, модель является фабрикой, конфигом и конфигуратором для компонента валидации, в добавок еще и использует сама. Это работает, но как выяснили, далеко не во всех ситуациях.
2) Zend framework. Действительно, почему бы и нет? Лично я не выбрал его, так как для него приходится много допиливать в реальной работе - в пакете нет декларативного конфига, в котором можно явно указать, как валидировать тот или иной класс.
3) Symfony 2 validation component - вот то, что пришлось мне по вкусу! Это validation framework, c декларативным конфигом, с большой возможностью расширения существующего функционала, и кроме прочего, основан на спецификации jsr303
Перепишем немного код на использование symfory
class Customer
{
public $firstName;
public $lastName;
public $age;
public $gender;
}
class SomeController
{
public function actionBuy()
{
$customer = $this->getCustomer();
$errors = $this->getValidator()->validate($customer);
if(count($errors) == 0)
{
echo "Correct!";
}
else
{
echo "Invalid! list on errors:";
foreach($errors as $error) echo _($error->getMessage());
}
}
}
Для того, чтобы использовать этот компонент, нужен дистрибутив symfony 2, и в bootstrap файле вашего приложения подключить библиотеку и инициализировать ее при помощи конфига. Наш конфиг в формате yaml может выглядеть так:
Customer:
properties:
lastName:
- NotBlank: ~
age:
- NotBlank: ~
- Type: { type: "numeric" }
- Min: { limit: 18 }
gender:
- NotBlank: ~
- Choice: { choices: ["male", "female"]}
Что получили: модель предоставляет контракт на использование, но понятия не имеет, как себя использовать, если что не так - кидает ошибку. В архитектуре не заложено, как именно валидировать обьект, точнее - логика домена не содержит код, который ее прямо не касается, такой как валидация. Вся логика, связанная с обсуживанием модели, вынесена за ее пределы и может как угодно видоизменяться и расширяться - например, мы можем описать свой валидатор с хитрозаумными правилами, и использовать его где хочется.
Итог - низкая связанность кода, прозрачное изменение компонентов, масштабирование логики и отсутствие избыточного кода, логику которого перенесли в декларативную часть - в конфиг.
Much appreciated for the information and share!
ОтветитьУдалить