06Модели
Атрибуты и метки, сценарии, правила валидации, безопасное массовое присваивание и экспорт модели в массив.
Модель — это данные + правила их проверки + бизнес-логика. Базовый класс yii\base\Model даёт атрибуты, метки, сценарии, валидацию, массовое присваивание и toArray(). Active Record наследует всё это и добавляет работу с таблицей.
Атрибуты#
Атрибуты — это публичные нестатические свойства класса. К ним можно обращаться и как к свойствам, и как к элементам массива:
class ContactForm extends \yii\base\Model
{
public $name;
public $email;
public $subject;
public $body;
}
$model = new ContactForm();
$model->name = 'Иван';
$model['email'] = 'ivan@example.com'; // ArrayAccess
foreach ($model as $attr => $value) { } // TraversableСписок атрибутов возвращает attributes(); Active Record переопределяет его и берёт имена столбцов таблицы.
Метки и подсказки#
По умолчанию метка генерируется из имени: firstName → «First Name». Явно — в attributeLabels(); подсказки для форм — в attributeHints():
public function attributeLabels()
{
return [
'name' => Yii::t('app', 'Ваше имя'),
'email' => Yii::t('app', 'E-mail'),
];
}
public function attributeHints()
{
return ['email' => 'Мы не будем присылать спам'];
}
echo $model->getAttributeLabel('name'); // Ваше имяСценарии#
Одна модель — разные ситуации: при регистрации email обязателен, при входе — нет. Сценарий хранится в свойстве scenario (по умолчанию default):
$model = new User(['scenario' => User::SCENARIO_LOGIN]);
// или
$model->scenario = User::SCENARIO_LOGIN;Сценарий определяет активные атрибуты — те, что проходят валидацию и могут быть присвоены массово. По умолчанию сценарии выводятся из правил rules() (ключи on/except), но их можно объявить явно:
class User extends ActiveRecord
{
const SCENARIO_LOGIN = 'login';
const SCENARIO_REGISTER = 'register';
public function scenarios()
{
$scenarios = parent::scenarios(); // сохраняем сценарии из rules()
$scenarios[self::SCENARIO_LOGIN] = ['username', 'password'];
$scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password'];
return $scenarios;
}
public function rules()
{
return [
[['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER],
[['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN],
['email', 'email'], // во всех сценариях
];
}
}Правила валидации#
public function rules()
{
return [
[['name', 'email', 'subject', 'body'], 'required'],
['email', 'email'],
['subject', 'string', 'max' => 100, 'on' => 'contact'],
// именованное правило — легко удалить в наследнике
'password' => [['password'], 'string', 'min' => 8],
];
}
if ($model->validate()) {
// всё хорошо
} else {
$errors = $model->errors; // ['email' => ['Email is not a valid email address.']]
$model->getFirstError('email');
$model->hasErrors();
}Формат правила: [атрибуты, валидатор, 'on' => сценарии, 'except' => сценарии, свойство => значение…]. Валидатор — псевдоним встроенного (required, email, string…), имя метода модели или класс. Полный разбор — в разделах Валидация и Встроенные валидаторы.
Массовое присваивание#
Одной строкой вместо десятка $model->x = $data['x']:
$model->attributes = Yii::$app->request->post('ContactForm'); // напрямую
$model->load(Yii::$app->request->post()); // то же, но сам возьмёт $_POST[formName()]load() возвращает true, если данные для модели вообще были в массиве — это удобно для «показать форму или обработать».
Безопасные атрибуты#
Массово присваиваются только безопасные атрибуты — активные в текущем сценарии. Остальные молча игнорируются. Так пользователь не сможет через форму выставить себе role = admin.
public function rules()
{
return [
[['title', 'description'], 'safe'], // безопасны без проверки
];
}
public function scenarios()
{
return [
// ! — проверять, но не присваивать массово
self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
];
}
$model->secret = $value; // только явноЭкспорт в массив#
$array = $model->attributes; // все атрибуты
$array = $model->toArray(); // поля из fields()
$array = $model->toArray([], ['prettyName', 'fullAddress']); // + поля из extraFields()fields() определяет, что попадёт в массив по умолчанию, extraFields() — что можно запросить дополнительно (так работает ?expand= в REST):
public function fields()
{
return [
'id',
'email' => 'email_address', // переименование
'name' => function () { // вычисляемое поле
return $this->first_name . ' ' . $this->last_name;
},
];
}
// или: взять родительские поля и убрать чувствительные
public function fields()
{
$fields = parent::fields();
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}Лучшие практики#
- Модели — толстые, контроллеры — тонкие: бизнес-логика живёт здесь.
- Модель не должна лезть в
$_GET, сессию или запрос — эти данные ей передаёт контроллер. - Никакого HTML в моделях.
- Не плодите сценарии в одной модели; для сложных форм заводите отдельные классы (
LoginForm,SignupForm). - В больших проектах: базовая модель в
common\models\Postс общими правилами, наследникиfrontend\models\Postиbackend\models\Post— со своей спецификой.
load() сам находит данные по имени формы ($data['ContactForm']) и возвращает false, если их нет; присваивание attributes работает с уже «распакованным» массивом. Оба присваивают только безопасные атрибуты.
Он не является безопасным: безопасные атрибуты — это активные атрибуты текущего сценария, а по умолчанию сценарии строятся из правил. Добавьте правило safe.
Атрибут валидируется, но не присваивается массово — только явно через $model->secret = ....
Переопределить fields() и убрать поле из массива родительской реализации.
В rules() через 'on' => 'login' / 'on' => 'register' или явно перечислив активные атрибуты в scenarios().