воскресенье, 29 апреля 2012 г.

Yii validation rules, зависящие от значений параметров модели

Задача

Cоздать правила валидации, зависящие от значений параметров модели.


В коде:
public function rules() {
 //обычные валидаторы
 $res = array(
  array('attr_name', 'validator'),
 );
 
 //валидаторы, добавляемые по условию
 if ($this->attr_name == 'some_value')
  $res[] = array('attr_name', 'validator');

 return $res;
}

Сначала опишу как решить проблему, а потом - почему такая проблема вообще возникает.

Дисклеймер: написанное справедливо для Yii PHP Framework 1.1.8, но поскольку для решения проблемы использовалась актуальная документация, то скорее всего проблема сохраняется и в текущей версии.

Решение

Изменяем модель следующим образом:
class ModelName extends CActiveRecord {
 //переопределяем свойство, чтобы иметь доступ к нему
 private $_validators;

 /*
  *
  * содержание модели
  *
  */

 public function getValidatorList() {
  if ($this->_validators === null)
   $this->_validators = $this->createValidators();
  return $this->_validators;
 }

 public function getValidators($attribute = null) {
  if ($this->_validators === null)
   $this->_validators = $this->createValidators();
 
  $validators = array();
  $scenario = $this->getScenario();
  foreach ($this->_validators as $validator) {
   if ($validator->applyTo($scenario)) {
   if ($attribute === null || in_array($attribute, $validator->attributes, true))
    $validators[] = $validator;
   }
  }
  return $validators;
 }

 public function resetValidators() {
  $this->_validators = null;
 }
}

Функции getValidatorList() и getValidators() копируются из исходных кодов CModel без изменений. Далее весь фокус заключается в использовании написанной нами функции resetValidators(). Рассмотрим на примере стандартного контроллера:

public function actionUpdate($id) {
 
 /*
  * содержание экшна
  */

 if (isset($_POST['ModelName'])) {
  $model->attributes = $_POST['ModelName'];
  //сброс валидаторов для обновления правил валидации
  //в соответствии с изменёнными параметрами модели
  $model->resetValidators();

  if ($model->save()) {
   $this->redirect(array('view', 'id' => $model->id));
  }

  
 }

 //сброс валидаторов для корректного отображения (*) на форме
 //(required-аттрибутов)
 $model->resetValidators();
        
 $this->render('update', array(
  'model' => $model,
 ));
}

Почему так непросто

< подробнее будет позже, может быть : ) >
Если кратко, то массив с правилами валидации генерируется для модели один раз - при первом обращении к правилам валидации (кажется). Наша задача - обновлять эти правила. А дальше начинаются костыли с копированием методов, потому что в оригинальном CModel аттрибут $_validators является private. Был бы он protected - было бы интереснее. В данной ситуации также можно переопределить