Neurons to bytes

Почему валидация в Zend_Form это плохо, или о том, как делать правильную валидацию

В настоящий момент Zend Framework имеет набор валидаторов, которые обычно используются для проверки входных данных из форм. Для этого используется пакет Zend_Form, отдельно же валидаторы используются достаточно редко.

Вроде бы всё хорошо, все пользуются и все довольны. Но если подумать, то в ZF валидация располагается не на том уровне, на котором хотелось бы. Я уверен, что валидация входных данных должна производиться именно в моделях и ни в коем случае не в формах, как сделано на даный момент. Почему? Остановимся на этом подробнее.

Представьте, что у вас есть форма для добавления поста в блог. Соответственно, есть класс, наследуемый от Zend_Form, который определяет, какие поля будут в форме, устанавливает на них валидаторы и, возможно, декораторы. В итоге мы получаем готовую форму, которую достаточно только вывести в нужное место шаблона - и можно использовать. Все будет валидироваться, и вроде бы все прекрасно.

Но представьте, что в один прекрасный момент вам понадобится написать робота, который должен будет опубликовывать посты, приходящие на указанный e-mail. То есть скрипт должен заходить на почтовый ящик через некоторые промежутки времени, получать по некоторым критериям письма, парсить их и опубликовывать в блоге. Получается, что форма тут не нужна - мы добавляем письма напрямую, минуя форму. А вдруг парсинг письма пройдет неправильно? Мы запросто можем получить пустой пост в блоге, или некорректный, если произойдут ошибки при парсинге.

Что делать? Чтобы избежать подобных ситуаций, придется добавлять валидатор и в модель, которая отвечает за добавление поста. Но получается, что теперь валидаторы будут указаны в двух разных местах - в форме и в модели. Это ужасно.

Чтобы избежать подобных противоречий, необходимо определять валидаторы только в конечной точке, отвечающей за непосредственное добавление поста в БД - конечно же в модели, и забыть про использование валидаторов в форме.

Кто-то скажет: так ведь форма не обязательно может добавлять данные в БД, она, например, может отправлять письмо на ту же почту, или делать что-то другое, не работая с БД. Зачем тогда определять валидаторы в модели?

Тут нужно понять, что модель - это не та штука, которая предназначена только для добавления данных в БД. Модель - это бизнес-логика, это то место, где происходит обработка поступивших данных. В Zend Framework мы имеем только один вид модели - Zend_Db_Table, и эта модель работает только с таблицами БД. Мы пока не имеем нечто среднее - модель, которая умеет работать и с таблицей и без ее использования. Когда мы получим такую функциональность, тогда мы сможем определять валидаторы в модели, будь это модель, добавляющая данные в таблицу БД, либо же не имеющая таблицы.

Я не знаю, есть ли какие-то подвижки в сторону “правильных моделей” у команды разработчиков ZF, но я очень надеюсь, что подобные желания у них есть. В любом случае, даже если это не так, я сам возьмусь за написание описанной мною функциональности.

Спасибо, надеюсь вам было интересно :)

Also interesting

Tag after , , , ,

  1. san says:

    Модель в моем понимании это то что не поставляется ни в каком виде фреймворком, это логика приложения. Zend_Db_Table всего лишь предоставляет способ удобного доступа в БД.
    А вообще рассуждения весьма интересные, сам об этом думал недавно. Интересно было бы увидеть вариант реализации подобной идеи.

    P.S. Добавил ваш блог в каталог.

    • Ouch! says:

      2 san:

      Да, вы правы, модель - это, конечно же, не таблица в БД. Это то “мясо” приложения, которое находится на костях - контроллере (если можно провести такую аналогию). Просто чаще всего, в приложениях которые каждый из нас делает, это наша бизнес-логика завязана в основном на БД. И нам бы нужно какой-то инструмент, чтобы было удобнее работать с подобными вещами - но увы, его пока нет. Приходится обходиться.
      Я пока смотрю в сторону Rails - очень уж там удобно сделана работа с моделями. Очень :) Конкретной реализации у меня пока нет, есть только кое какие наброски, килобайт на 30 кода. Тут наверное нужно единомышленников искать - одному это можно годами делать. Потом как-нибудь напишу пост на эту тему.

      Спасибо за добавление в каталог :)

  2. lcf says:

    “Для этого используется пакет Zend_Form, отдельно же валидаторы используются достаточно редко.”
    Ничего не знаю, я использую валидаторы для валидации. формы тут вообще не причем. То что в них можно встроить валидаторы - удобная фича.

    “Но представьте, что в один прекрасный момент вам понадобиться написать робота, который должен будет опубликовывать посты, приходящие на указанный e-mail. То есть скрипт должен заходить на почтовый ящик через некоторые промежутки времени, получать по некоторым критериям письма, парсить их и опубликовывать в блоге. Получается, что форма тут не нужна - мы добавляем письма напрямую, минуя форму. А вдруг парсинг письма пройдет неправильно? Мы запросто можем получить пустой пост в блоге, или некорректный, если произойдут ошибки при парсинге.”

    Фишка в том, что валидаторы не обязательно должны быть использованны вместе с формой. Вы можете взять валидатор (который сам по себе является моделью реализующей валидирующую часть упомянутой вами бизнес логики) где угодно. В скрипте-роботе, для валидации объекта запроса… ваще где угодно. Таккой подход/вынесение валидации в отдельную модель нравится гораздо больше. Он предоставляет тот же функционал, но более диференцированно, гораздо гибче. Я могу валидировать данные где угодно когда угодно, мне не надо вызывать методы модели Книга чтобы узнать валидны ли какие либо данные.

    Конечные же модели должны генерировать исключения в исключительных ситуациях,

    Где что я не вижу? Где узкие места?

    • Ouch! says:

      2 lcf:

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

      Это так. Но я видел что люди его чаще всего используют именно в формах. Наверное, это зависит от специфики проекта.

      Я могу валидировать данные где угодно когда угодно, мне не надо вызывать методы модели Книга чтобы узнать валидны ли какие либо данные. … Где что я не вижу? Где узкие места?

      Мысль понятна. Но так ли это накладно будет вызвать нужные методы у модели, особенно если это было бы сделано удобно? Тем более, где еще нужно валидировать данные, как не в модели? По-моему это и есть та самая точка, где требуется валидация пришедших данных, потому, как после этой точки с данными что-то происходит: они записываются в БД, посылаются на e-mail, или отправляются на дальнейшую обработку куда-либо.

  3. lcf says:

    Как подписаться на уведомление о новых комментах? Можно как нить?

  4. Zh0rzh says:

    А еще робот паук может сделать так :)

    $data = …;

    $form = new App_MyForm();
    if($form->isValid($data)) {

    }

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

    Плюс есть мнение - что объекты форм тоже можно отнести к части модели

  5. giovanni says:

    а наиболее острый вопрос не в том где валидировать, а в том КАК узнает клиент (форма) о требуемом формате данных у сервера (модели). потому что валидировать с последующим выбросом exception - это не валидация, а просто sanity check.

    вот как сделать так модель, чтобы она не просто валидировала, но и ДЕКЛАРИРОВАЛА свою валидационную составляющую

    • Ouch! says:

      Смотрите, если я вас правильно понял, то тогда примерно так.

      Если делать так, что в модели будут валидироваться входящие данные, то, можно указывать валидаторы наподобие валидаторов в Rails. Соответственно, модель теперь знает, как валидировать свои данные (она хранит определенные валидаторы внутри объекта). Теперь, если мы передадим объект модели в view script, а в нем в какой-либо хелпер вида, который сгенерирует нам javascript-код для валидации формы.

      И какой такой формат данных может быть у модели?
      Все же, мне кажется, моя твоя непониль :)

      Жду ответа.

  6. giovanni says:

    смотри пример. есть класс Employee, у которого есть свойство age. в итоге имеем $employee->age. у этого свойства есть ряд мета-свойств. а именно:
    - диапазон значений
    - смысл (что это вообще за свойство)
    - условия допустимости (например if ($sex == ‘female’) $age < 60 else $age < 65)
    - и куча всего прочего

    теперь имеем две формы. разные. в одной мы хотим назначить возраст сотруднику. в другой мы хотим этот возраст редактировать на основании чего-то (допустим это разные формы).

    теперь я хочу чтобы инициализация и конфигурация формы (!) происходила автоматом, на основании мета-данных из моего класса Employee. я хочу чтобы форма САМА поняла, что можно а что нельзя делать с этим свойством $age.

    и форма и кто угодно любой другой. и поняла и получила бы адекватный ответ из класса Employee…

    хочется создать такой механизм

    • Ouch! says:

      Ага, теперь мне все стало понятно. Надо придумать, как указывать эти “мета-свойства” в модели.

      Гляну, как в Ruby ActiveRecord подобное реализуется (наверняка там есть что-то похожее).

  7. Dr0n says:

    В моем понимании модель - это объект для работы с данными, не важно с какими данными (таблица в БД, набор таблиц, файловая система). И валидировать данные в модели, в моем понимании, не правильно. Потому что в различных местах проекта для одной и той же модели, могут быть присущи различные правила валидации.
    При подходе с валидацией данных в модели Вам придется отслеживать все эти участки и учитывать при проектировании модели.
    Валидация данных должна происходить в контроллере, и даже не обязательно в объекте Form (как писалось выше это просто удобная фича).
    Для валидации у себя в проектах я использую цепочки валидации применяемые к моделям. Т.е. до того как данные будут сохранены.
    Цепочки валидации тоже можно вынести в модели, если используются одинаковые в разных местах.
    В общем смысле есть модель в ней `сохранены` данные с поста, гета. К модели применяются цепочки валидации, если все валидно, то у модели вызывается метод save который уже сохраняет данные используя DbTable, если данные не валидны, то выводится причина ошибки - все это происходит в контроллере. Кому интересно могу объяснить подробнее.

    • giovanni says:

      валидация данных ВНЕ места их хранения - это нарушение принципа decoupling. что в итоге приведет к разрыву связи между данными и принципами их проверки. рано или поздно мы изменим данные и забудем где находится валидация. и валидацию не изменим. в итоге получим tight coupling, что есть зло.

      в общем я категорически не согласен. могу объяснить подробнее :)

      • Ouch! says:

        Согласен с вами и так же не согласен с Dron’ом. Вы привели правильные доводы, именно поэтому, я и предложил производить валидацию непосредственно в модели.

        • giovanni says:

          То что в модели - тут двух мнений быть не может. А вот как потом результат этой валидации “доносить” доходчиво до всех клиентов - тут разногласия. Я считаю, что через Exceptions. Когда модель получает невалидные данные - она бросает exception. А форма должна понимать это и текст этого исключения писать под кнопкой SUBMIT. Я так вижу и так оно у меня работает. Вроде бы все логично.

          • Ouch! says:

            У меня эксепшенами не бросается, а просто собирает список ошибок валидаторов в массив:

            $user = new User;
            $user->name = ”;

            if (!$user->save()) { // save запускает валидацию
            print_r($user->getErrorMessages());
            }

            Как-то так. В общем, если интересно, могу показать свою ORM :) Вообще, ищу единомышленников для создания ORM ;)

            • giovanni says:

              все вроде верно, только я не понял про print_r().. куда печатать то будем?

              напиши мне на емейл, пообщаемся в скайпе.

              • Ouch! says:

                print_r() - это для примера. На самом деле, я передаю объект модели в вид, и там происходит “магия” - в общем, это лучше увидеть.)

    • Dr0n says:

      Нверное не совсем корректно описал идею, напишу немного подробнее.
      Согласен с тем, что не правильно разделять хранение данных и валидацию, но хранить валидаторы в модели которая отвечает за эти данные тоже считаю неверным подходом. Считаю что валидироваться в модели должны только некорректные данные которые могут привести к ексепшену при сохранении/обновлении.

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

      Набором валидаторов применяемых в конкретной ситуации может выступать отдельная модель-валидатор. Которая сможет проверить данные в соответствии со своими заданными правилами валидации. Эту модель можно применять для валидации других моделей-данных. Вот как то так я себе все это представляю.

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

  8. Dr0n says:

    Забыл написать что это все касается серверной валидации.


(required)


(required but won't be displayed)