Иногда бывает так, что одну и ту же модель нужно валидировать по разному в зависимости от значений некоторых полей. Выдумаем такой пример - есть модель Worker у которой есть поля опыт и размер зарплаты.
class Worker extends CModel {
public $experience;
public $salary;
public function attributeNames()
{
return ['experience', 'salary'];
}
}
Теперь есть задача валидировать размер зарплаты в зависимости от размера опыта. Если опыт от 0 до 5, то границы зарплаты от 1000 до 5000. Если опыт больше 5, то границы зарплаты от 5000 до 10000. В простом случае (если бы поля не зависели друг от друга) мы бы написали простые правила для валидации:
public function rules()
{
return [
['experience, salary', 'required'],
['salary', 'numerical', 'integerOnly' => true, 'min' => 1000, 'max' => 10000],
];
}
В нашем случае напрашивается такое решение:
public function rules()
{
$minSalary = 1000;
$maxSalary = 5000;
if ($this->experience > 5) {
$minSalary = 5000;
$maxSalary = 10000;
}
return [
['experience, salary', 'required'],
['salary', 'numerical', 'integerOnly' => true, 'min' => $minSalary, 'max' => $maxSalary],
];
}
Но работать это будет только когда вы напрямую присваиваете значения атрибутам не пользуясь для этого свойством attributes
. А, например, в таком случаем ваше правило отработает неправильно:
$worker = new Worker();
$worker->attributes = [
'experience' => 8,
'salary' => 6000,
];
$worker->validate();
Проблема в том, что метод rules
вызывается только один раз, чтобы создать список валидаторов и затем всегда используется уже сгенерированный список генераторов который хранится в приватном поле $_validators
. А в приведенном примере список валидаторов был сгенерирован когда происходило присвоение атрибутов модели - он нужен чтобы узнать какие атрибуты разрешены для заполнения.
Первое же простое решение которое приходит на ум - это всегда генерировать список валидаторов перед выполнением валидации. Несмотря на то что поле $_validators
приватное, манипулировать списком валидаторов все таки можно:
public function validate($attributes = null, $clearErrors = true)
{
$vl = $this->getValidatorList();
$vl->clear();
$vl->mergeWith($this->createValidators());
return parent::validate($attributes, $clearErrors);
}
Мы переопределили метод validate
в нашей модели и теперь при каждой валидации создаем список валидаторов. Единственный минус этого решения - страдает производительность.
Другой способ, на мой взгляд более правильный, это написать собственный inline валидатор:
public function rules()
{
return [
['experience, salary', 'required'],
['salary', 'numerical', 'integerOnly' => true],
['salary', 'salaryValidator'],
];
}
public function salaryValidator($attribute)
{
$minSalary = 1000;
$maxSalary = 5000;
if ($this->experience > 5) {
$minSalary = 5000;
$maxSalary = 10000;
}
if ($this->$attribute < $minSalary) {
$this->addError($attribute, '...');
}
if ($this->$attribute > $maxSalary) {
$this->addError($attribute, '...');
}
}
Таким образом не происходит постоянной перегенерации всего списка валидаторов и логика по валидации зарплаты вынесена в отдельный метод и не замусоривает метод rules
.