Модели
Модели являются частью архитектуры MVC. Они представляют собой объекты, содержащие данные, правила и логику бизнес-логики.
Вы можете создавать классы моделей, расширяя yii\base\Model или его дочерние классы. Базовый класс yii\base\Model поддерживает множество полезных функций:
- Атрибуты: представляют бизнес-данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов: указывают метки отображения для атрибутов;
- Массовое присвоение: поддерживает заполнение нескольких атрибутов в одном шаге;
- Правила валидации: гарантирует соответствие входных данных указанным правилам валидации;
- Экспорт данных: позволяет экспортировать данные модели в виде массивов с настраиваемыми форматами.
Класс Model также является базовым классом для более продвинутых моделей, таких как Active Record. Для получения более подробной информации о этих расширенных моделях, обратитесь к соответствующей документации.
Информация: Вам не обязательно использовать yii\base\Model в качестве базового класса для ваших моделей. Однако, так как многие компоненты Yii построены с поддержкой yii\base\Model, это обычно предпочтительный базовый класс для моделей.
Атрибуты
Модели представляют бизнес-данные в виде атрибутов. Каждый атрибут подобен общедоступному свойству модели. Метод yii\base\Model::attributes() определяет атрибуты класса модели.
Вы можете получить доступ к атрибуту, как к обычному свойству объекта:
$model = new \app\models\ContactForm; // "name" is an attribute of ContactForm $model->name = 'example'; echo $model->name;
Вы также можете получить доступ к атрибутам, как к элементам массива, благодаря поддержке ArrayAccess и Traversable классом yii\base\Model:
$model = new \app\models\ContactForm;
// accessing attributes like array elements
$model['name'] = 'example';
echo $model['name'];
// Model is traversable using foreach.
foreach ($model as $name => $value) {
echo "$name: $value\n";
}
Определение Атрибутов
По умолчанию, если ваш класс модели непосредственно наследуется от yii\base\Model, все его нестатические публичные переменные-члены являются атрибутами. Например, класс модели ContactForm ниже имеет четыре атрибута: name, email, subject и body. Модель ContactForm используется для представления входных данных, полученных из HTML-формы.
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
Вы можете переопределить yii\base\Model::attributes(), чтобы определить атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например, yii\db\ActiveRecord делает это, возвращая имена столбцов связанной таблицы базы данных в качестве имен атрибутов. Обратите внимание, что вам также может потребоваться переопределить магические методы, такие как __get(), __set(), чтобы атрибуты могли быть доступны как обычные свойства объекта.
Метки Атрибутов
При отображении значений или получении входных данных для атрибутов, вам часто необходимо отобразить метки, связанные с атрибутами. Например, для атрибута с именем firstName, вы можете захотеть отобразить метку First Name, которая более удобна для пользователей при отображении в таких местах, как поля форм и сообщения об ошибках.
Вы можете получить метку атрибута, вызвав yii\base\Model::getAttributeLabel(). Например,
$model = new \app\models\ContactForm;
// displays "Name"
echo $model->getAttributeLabel('name');
По умолчанию метки атрибутов автоматически генерируются из имён атрибутов. Генерация выполняется методом yii\base\Model::generateAttributeLabel(). Он преобразует имена переменных с нижним регистром с camelCase в слова с заглавными буквами в каждом слове. Например, username становится Username, а firstName становится First Name.
Если вы не хотите использовать автоматически сгенерированные метки, вы можете переопределить yii\base\Model::attributeLabels(), чтобы явно объявить метки атрибутов. Например,
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
public function attributeLabels()
{
return [
'name' => 'Your name',
'email' => 'Your email address',
'subject' => 'Subject',
'body' => 'Content',
];
}
}
Для приложений, поддерживающих несколько языков, вам может потребоваться перевести метки атрибутов. Это можно сделать в методе attributeLabels(), как показано ниже:
public function attributeLabels()
{
return [
'name' => \Yii::t('app', 'Your name'),
'email' => \Yii::t('app', 'Your email address'),
'subject' => \Yii::t('app', 'Subject'),
'body' => \Yii::t('app', 'Content'),
];
}
Вы даже можете условно определять метки атрибутов. Например, в зависимости от сценария, в котором используется модель, вы можете возвращать разные метки для одного и того же атрибута.
Информация: Строго говоря, метки атрибутов являются частью представлений. Но объявление меток в моделях часто очень удобно и может привести к очень чистому и переиспользуемому коду.
Сценарии
Модель может использоваться в разных сценариях. Например, модель User может использоваться для сбора входных данных для входа пользователя, но также может использоваться для регистрации пользователя. В разных сценариях модель может использовать разные бизнес-правила и логику. Например, атрибут email может быть обязательным при регистрации пользователя, но не обязательным при входе пользователя.
Модель использует свойство yii\base\Model::$scenario, чтобы отслеживать сценарий, в котором она используется. По умолчанию модель поддерживает только один сценарий с именем default. Следующий код показывает два способа установки сценария модели:
// scenario is set as a property $model = new User; $model->scenario = User::SCENARIO_LOGIN; // scenario is set through configuration $model = new User(['scenario' => User::SCENARIO_LOGIN]);
По умолчанию, поддерживаемые сценарии модели определяются правилами валидации, объявленными в модели. Однако вы можете настроить это поведение, переопределив метод yii\base\Model::scenarios(), как показано ниже:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
const SCENARIO_LOGIN = 'login';
const SCENARIO_REGISTER = 'register';
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
}
Информация: В приведенных выше и последующих примерах классы моделей наследуются от yii\db\ActiveRecord, потому что использование нескольких сценариев обычно происходит в классах Active Record.
Метод scenarios() возвращает массив, ключи которого являются именами сценариев, а значения — соответствующими активными атрибутами. Активный атрибут может быть массово присвоен и подлежит валидации. В приведённом выше примере атрибуты username и password активны в сценарии login; в то время как в сценарии register, атрибут email также активен помимо атрибутов username и password.
Стандартная реализация scenarios() вернёт все сценарии, найденные в объявлении правил валидации в методе yii\base\Model::rules(). При переопределении scenarios(), если вы хотите добавить новые сценарии помимо стандартных, вы можете написать код, как показано ниже:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
const SCENARIO_LOGIN = 'login';
const SCENARIO_REGISTER = 'register';
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_LOGIN] = ['username', 'password'];
$scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password'];
return $scenarios;
}
}
Функция сценариев используется в первую очередь для валидации и массового присвоения атрибутов. Однако вы можете использовать её и для других целей. Например, вы можете объявить метки атрибутов по-разному в зависимости от текущего сценария.
Правила валидации
Когда данные для модели получены от пользователей, их следует проверить, чтобы убедиться, что они удовлетворяют определённым правилам (называемым правилами валидации, также известными как бизнес-правилами). Например, для модели ContactForm вы можете захотеть убедиться, что все атрибуты не пусты и атрибут email содержит корректный адрес электронной почты. Если значения некоторых атрибутов не соответствуют соответствующим бизнес-правилам, должны отображаться соответствующие сообщения об ошибках, чтобы помочь пользователю исправить ошибки.
Вы можете вызвать yii\base\Model::validate() для проверки полученных данных. Метод будет использовать правила валидации, объявленные в yii\base\Model::rules(), для проверки каждого соответствующего атрибута. Если ошибок нет, он вернёт true. В противном случае он сохранит ошибки в свойстве yii\base\Model::$errors и вернёт false. Например,
$model = new \app\models\ContactForm;
// populate model attributes with user inputs
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// all inputs are valid
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
}
Для объявления правил валидации, связанных с моделью, переопределите метод yii\base\Model::rules(), вернув правила, которым должны удовлетворять атрибуты модели. Следующий пример показывает правила валидации, объявленные для модели ContactForm:
public function rules()
{
return [
// the name, email, subject and body attributes are required
[['name', 'email', 'subject', 'body'], 'required'],
// the email attribute should be a valid email address
['email', 'email'],
];
}
Правило может использоваться для проверки одного или нескольких атрибутов, и атрибут может проверяться одним или несколькими правилами. Подробности о том, как объявлять правила валидации, см. в разделе Валидация входных данных.
Иногда вам может потребоваться, чтобы правило применялось только в определённых сценариях. Для этого вы можете указать свойство on правила, как показано ниже:
public function rules()
{
return [
// username, email and password are all required in "register" scenario
[['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER],
// username and password are required in "login" scenario
[['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN],
];
}
Если вы не укажете свойство on, правило будет применено ко всем сценариям. Правило называется активным правилом, если оно может быть применено в текущем сценарии.
Атрибут будет проверен только в том случае, если он является активным атрибутом, объявленным в scenarios(), и связан с одним или несколькими активными правилами, объявленными в rules().
Массовое присвоение
Массовое присвоение — удобный способ заполнения модели входными данными пользователя с помощью одной строки кода. Оно заполняет атрибуты модели, присваивая данные ввода непосредственно свойству yii\base\Model::$attributes. Следующие две строки кода эквивалентны, обе пытаются назначить данные формы, отправленные пользователями, атрибутам модели ContactForm . Очевидно, что первый вариант, использующий массовое присвоение, гораздо чище и менее подвержен ошибкам, чем второй:
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
$model = new \app\models\ContactForm;
$data = \Yii::$app->request->post('ContactForm', []);
$model->name = isset($data['name']) ? $data['name'] : null;
$model->email = isset($data['email']) ? $data['email'] : null;
$model->subject = isset($data['subject']) ? $data['subject'] : null;
$model->body = isset($data['body']) ? $data['body'] : null;
Безопасные атрибуты
Массовое присвоение значений применяется только к так называемым безопасным атрибутам, которые являются атрибутами, перечисленными в yii\base\Model::scenarios() для текущего сценария модели. Например, если у модели User есть следующее объявление сценария, то при текущем сценарии login будут массово присваиваться только username и password. Любые другие атрибуты останутся без изменений.
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
Информация: Причина, по которой массовое присвоение значений применяется только к безопасным атрибутам, заключается в том, что вы хотите контролировать, какие атрибуты могут быть изменены данными конечного пользователя. Например, если у модели
Userесть атрибутpermission, определяющий разрешения, назначенные пользователю, вы хотите, чтобы этот атрибут изменялся только администраторами через интерфейс бэкэнда.
Поскольку по умолчанию реализация yii\base\Model::scenarios() возвращает все сценарии и атрибуты, найденные в yii\base\Model::rules(), если вы не переопределяете этот метод, атрибут считается безопасным, если он присутствует в одном из правил валидации.
По этой причине предоставляется специальный валидатор с псевдонимом safe, позволяющий объявить атрибут безопасным без его фактической валидации. Например, следующие правила объявляют, что title и description являются безопасными атрибутами.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Небезопасные атрибуты
Как описано выше, метод yii\base\Model::scenarios() служит для двух целей: определения атрибутов, которые должны быть валидированы, и определения безопасных атрибутов. В редких случаях вы можете захотеть валидировать атрибут, но не отмечать его как безопасный. Вы можете сделать это, добавив префикс «!» к имени атрибута при его объявлении в scenarios(), как в случае с атрибутом secret в следующем примере:
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
];
}
Когда модель находится в сценарии login, все три атрибута будут валидированы. Однако только атрибуты username и password могут быть массово назначены. Чтобы присвоить значение вводу атрибута secret, необходимо сделать это явно, как показано ниже:
$model->secret = $secret;
То же самое можно сделать в методе rules():
public function rules()
{
return [
[['username', 'password', '!secret'], 'required', 'on' => 'login']
];
}
В этом случае атрибуты username, password и secret являются обязательными, но secret должен быть назначен явно.
Экспорт данных
Модели часто необходимо экспортировать в различных форматах. Например, вы можете преобразовать коллекцию моделей в JSON или Excel. Процесс экспорта можно разбить на два независимых шага:
- модели преобразуются в массивы;
- массивы преобразуются в целевые форматы.
Вы можете сосредоточиться только на первом шаге, так как второй шаг может быть выполнен с помощью универсальных форматировщиков данных, таких как yii\web\JsonResponseFormatter.
Самый простой способ преобразовать модель в массив — использовать свойство yii\base\Model::$attributes. Например,
$post = \app\models\Post::findOne(100); $array = $post->attributes;
По умолчанию свойство yii\base\Model::$attributes вернёт значения всех атрибутов, объявленных в yii\base\Model::attributes().
Более гибкий и мощный способ преобразования модели в массив — использование метода yii\base\Model::toArray(). Его поведение по умолчанию такое же, как у yii\base\Model::$attributes. Однако он позволяет выбрать, какие элементы данных, называемые полями, должны быть включены в результирующий массив, и как они должны быть отформатированы. Фактически, это стандартный способ экспорта моделей в RESTful веб-сервисах, как описано в разделе Форматирование ответов.
Поля
Поле — это просто именованный элемент в массиве, который получается при вызове метода yii\base\Model::toArray() модели.
По умолчанию имена полей эквивалентны именам атрибутов. Однако вы можете изменить это поведение, переопределяя методы fields() и/или extraFields(). Оба метода должны возвращать список определений полей. Поля, определённые в fields(), являются полями по умолчанию, то есть toArray() по умолчанию возвращает эти поля. Метод extraFields() определяет дополнительные доступные поля, которые также могут быть возвращены методом toArray(), если вы укажете их через параметр $expand. Например, следующий код вернёт все поля, определённые в fields(), а также поля prettyName и fullAddress, если они определены в extraFields().
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Вы можете переопределить fields(), чтобы добавить, удалить, переименовать или переопределить поля. Значение, возвращаемое fields(), должно быть массивом. Ключи массива — это имена полей, а значения — соответствующие определения полей, которые могут быть именами свойств/атрибутов или анонимными функциями, возвращающими соответствующие значения полей. В особом случае, когда имя поля совпадает с именем определяющего атрибута, вы можете опустить ключ массива. Например,
// explicitly list every field, best used when you want to make sure the changes
// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
'id',
// field name is "email", the corresponding attribute name is "email_address"
'email' => 'email_address',
// field name is "name", its value is defined by a PHP callback
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
// remove fields that contain sensitive information
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
Предупреждение: Поскольку по умолчанию все атрибуты модели включаются в экспортируемый массив, необходимо проверить ваши данные, чтобы убедиться, что они не содержат конфиденциальной информации. Если такая информация есть, необходимо переопределить
fields(), чтобы отфильтровать её. В приведённом примере мы выбрали отфильтроватьauth_key,password_hashиpassword_reset_token.
Рекомендации по лучшим практикам
Модели являются центральными местами для представления бизнес-данных, правил и логики. Они часто используются в разных местах. В хорошо спроектированном приложении модели обычно намного объёмнее, чем контроллеры.
Подводя итог, модели
- могут содержать атрибуты для представления бизнес-данных;
- могут содержать правила валидации для обеспечения валидности и целостности данных;
- могут содержать методы, реализующие бизнес-логику;
- не должны напрямую обращаться к запросу, сессии или любым другим данным среды. Эти данные должны быть переданы контроллерами в модели;
- должны избегать встраивания HTML или другого презенттационного кода — это лучше делать в представлениях;
- должны избегать наличия слишком многих сценариев в одной модели.
Обычно об этом последнем рекомендации стоит задуматься при разработке больших сложных систем. В таких системах модели могут быть очень большими, так как используются во многих местах и поэтому могут содержать много наборов правил и бизнес-логики. Это часто приводит к трудностям в обслуживании кода модели, так как даже небольшое изменение кода может повлиять на несколько разных мест. Чтобы сделать код модели более поддерживаемым, можно применить следующую стратегию:
- Определите набор базовых классов моделей, которые используются различными приложениями или модулями. Эти классы моделей должны содержать минимальные наборы правил и логики, которые являются общими для всех их применений.
- В каждом приложении или модуле, использующем модель, определите конкретный класс модели, расширив соответствующий базовый класс модели. Конкретные классы моделей должны содержать правила и логику, специфичные для этого приложения или модуля.
Например, в шаблоне проекта Advanced можно определить базовый класс модели common\models\Post. Затем для приложения front end определить и использовать конкретный класс модели frontend\models\Post, расширяющий common\models\Post. Аналогично, для приложения back end определить класс backend\models\Post. Благодаря этой стратегии код в frontend\models\Post будет специфичен только для приложения front end, и при внесении изменений в него не нужно беспокоиться о том, что они могут повлиять на приложение back end.
© 2008–2017 by Yii Software LLC
Licensed under the three clause BSD license.
http://www.yiiframework.com/doc-2.0/guide-structure-models.html