Данный пост родился в результате небольшой дискуссии на русскоязычной части форума Kohana. Как мы знаем, в Ko3 настройки валидации описаны в свойствах $_rules
, $_callbacks
и $_filters
. Они загружаются в объект Validate через метод ORM::_validate()
, в связи с чем возникает проблема — как добавлять правила в зависимости от каких-либо условий. Предлагаю одно из возможных решений.
Для тех, кому лень читать указанное обсуждение, опишу вкратце проблему. Есть запись, которую может оставить как зарегистрированный пользователь, так и гость. Вследствие этого в таблице есть как поле
`author_id`
(только для зарегистрированных пользователей), так и`author`
(там хранится имя, как гостя, так и пользователя). Проблема в том, что гость может использовать только незанятые пользователями имена, т.е. появляется дополнительное правило для некоторых записей в таблице. Правило должно применяться как при валидации новой записи, так и при редактировании существующей.
Освежим в памяти теорию
Один из главных девизов нового модуля ORM — «ленивая загрузка», так что инициализация свойства $_validate
начинается только после первого обращения к нему (а это может быть реализовано двумя методами — check()
и validate()
). За подготовку объекта Validate отвечает метод _validate()
, который создает его, добавляет правила (rules), вызовы (callbacks), фильтры (filters) и метки (labels). Данный метод выполняется, когда методы check()
и validate()
проверяют свойство $_validate
, и оно оказывается не заполнено.
Собственно сама проверка начинается после вызова check()
. В объект Validate автоматически подставляются значения полей модели ORM (свойство $_object
), включая несуществующие поля (указанные в свойстве $_ignored_columns
). Далее выполняется сама проверка ($this->_validate->check()
), как результат возвращается TRUE.
Ну уж теперь все ясно!
Конечно, дополнительные правила надо вставлять где-то в промежутке между проверкой и добавлением основных (общих для всех). Для этого подходит метод _validate()
. В своей модели мы можем расширить его, добавив в конце свои условия. Для описанной на форуме проблемы получится что-то вроде такого кода:
// classes/model/post.php protected function _validate() { // не забываем сперва добавить "родные" правила parent::_validate(); // добавляем проверку на доступность имени, если запись оставлена гостем if ( is_null($this->author_id) ) { $this->_validate->callback('author', array($this, 'author_available')); } } |
Чем отличается запись гостя от записи зарегистрированного пользователя? Поле `author`
заполнено и там, и там, а вот `author_id`
(идентификатор пользователя) у гостя должен быть NULL. На основании этого рассуждения мы добавляем дополнительную проверку.
Попутно…
В ходе тестов выяснилось, что «ленивая загрузка» не отрабатывает, если создавать объект с передачей идентификатора (типа ORM::factory('article', 1)
) и сразу вызывать метод check()
. Объект не будет загружен из БД, и в результате получим кучу ошибок валидации. Это в принципе логично, т.к. уже сохраненные объекты обычно не проверяют. Если все же надо сделать проверку, используйте явный вызов метода find()
(ORM::factory('article')->find(1)
), либо проверку методом loaded()
.
BIakaVeron, большое спасибо за статью. В очередной раз нашел для себя много нового и интересного. Но, может быть я что-то не так понимаю, как же все-таки данное решение может помочь именно в добавлении новых записей? Записи то еще не существует, поэтому нет и author_id, по которому проводится проверка.
Да, при редактировании уже имеющейся записи все это прекрасно работает, но также работало и мое решение, где внутри callback-функции выполнялась проверка загружена ли модель через $this->_loaded
1. Что значит не существует? Вы создаете объект ORM, заполняете его значениями (что-то из $_POST, информацию о пользователе из библиотеки Auth), потом выполняете проверку. Если author_id не установлен, то писал гость.
2. Использование $this->_loaded не позволит редактировать записи гостей (они уже сохранены, т.е. _loaded==TRUE).
Прошу прощения, похоже я действительно переутомился . Я же могу передать из формы и значение author_id. Так что все работает замечательно! Еще раз спасибо!
Вот уж действительно век живи, век учись. Служебную информацию в ORM можно передать непосредственно в контроллере, а не гонять через скрытые поля в форме . Ведь это же так очевидно!
пришел к выводу, что сам класс Kohana_Validate написан как-то неуклюже…
Вдруг не с того не с сего, Создателю пришло в голову, что раз нет правил, фильтров и колбэков, то и не следует это поле держать в объекте класса Kohana_Validate…
Это печально, я даже сказал бы очень печально…
С чего бы это вдруг?
Разве валидация пришедших значений из формы (из $_POST) проводится для отдельных переменных массива $_POST???
В принципе понятно, что если есть $_POST, то он пришел из формы…
И также логично, что если пользуемся ORM или Sprig, то и форма привязывается к модели…
Однако нет… например:
$validator = Validate::factory($_POST);
if($validator->check())
{
$post = $validator->as_array();
// вот здесь в массиве мы и не увидим
// те самые переменные для которых нет правил, фильтров и колбэков
}
Печально…
Иван, свои негодования я не Вам лично высказываю…
я бы выссказал на оф.форум, однако с инглишем не гуд…
имелось в виду:
$validator = Validate::factory($_POST)
->rules(…)
->filters(…);
Не Вы первый негодуете, и не Вы последний… На самом деле я есть смысл фильтровать лишние переменные при проверке. Представьте себе, что форма содержит данные для нескольких моделей (такое бывает). Зачем загружать все переменные в объект Validate и пытаться проверять их? И даже если поле существует в модели, зачем неявно загружать его в нее? Каждый должен заниматься своим делом, и Validate всего лишь проверяет на соответствие правилам. Объясните, почему данный класс должен обрабатывать какие-то посторонние переменные, которые в нем не описаны?
очень просто…
у меня как раз такой случай.
форма передает данные для 3х моделей.
у некоторых полей в модели нет правил, фильтров, колбэков.
Я считаю, что передача данных в модель должна происходить с использованием всего лишь одного метода.
Или должен быть механизм автоматической передачи данных в модель, причем как с правилами, так и без правил…
Иначе придется писать дополнительную обработку по добавлению переменных в объект модели, использовать всякие array_intersect’ы и тд и тп…
а это уже как-то не swift…
Спасибо, Иван. Я попробую.
не получается что-то…
если данные не проходят валидацию, то имеем
Validate_Exception [ 0 ]: Failed to validate array
я работаю со Sprig, а там у Создателя по-проще малость чем в ORM… при этом метод check() возвращает массив данных (зачем??? ). Ну а если валидация не проходит, то:
$data = Validate::factory($data);
…
…
…
if ( ! $data->check())
{
throw new Validate_Exception($data);
}
мда…
в целом вижу выход только один…
использовать функции работы с массивами после валидации, дабы воссоздать целостность данных перед передачей в модель перед сохранением данных.
Sprig использует другой принцип работы, там не требуется запускать валидацию вручную (читаем). Просто берем create() или update() и ловим Validate_Exception с ошибками.
Спасибо, Иван
читал уже, и не раз
и Sprig разобрал уже «по винтикам»…
04:57 22.01.2010, Woody Gilk, Kohana v3.x: Activity
The way validation works is clearly defined… As I said numerous times:
Any field that does not have a filter, rule, or callback is removed.
This is the way it works and is the way it has always worked. The reasons why have been clearly stated… I’m sorry you think it is wrong, but tough luck.
Вот ведь неугомонный какой
По мне так вполне логичных подход. Ведь любом учебнике «для чайников» русским по белому обязательно написано: «Всегда проверяйте данные, получаемые от пользователей». И если кое-где можно схалявить, то благодаря Woody Gilk в Kohana это делать обязательно