Модели ¶
Модели являются частью архитектуры MVC (Модель-Вид-Контроллер). Они представляют собой объекты бизнес данных, правил и логики.
Вы можете создавать классы моделей путём расширения класса yiibaseModel или его дочерних классов. Базовый класс yiibaseModel поддерживает много полезных функций:
- Атрибуты: представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов: задают отображение атрибута;
- Массовое присвоение: поддержка заполнения нескольких атрибутов в один шаг;
- Правила проверки: обеспечивают ввод данных на основе заявленных правил проверки;
- Экспорт Данных: разрешает данным модели быть экспортированными в массивы с настройкой форматов.
Класс Model
также является базовым классом для многих расширенных моделей, таких как Active Record. Пожалуйста, обратитесь к соответствующей документации для более подробной информации об этих расширенных моделях.
Информация: Вы не обязаны основывать свои классы моделей на yiibaseModel. Однако, поскольку в yii есть много компонентов, созданных для поддержки yiibaseModel, обычно так делать предпочтительнее для базового класса модели.
Атрибуты ¶
Модели предоставляют рабочие данные в терминах атрибутах. Каждый атрибут представляет собой публично доступное свойство модели. Метод yiibaseModel::attributes() определяет какие атрибуты имеет класс модели.
Вы можете получить доступ к атрибуту как к обычному свойству объекта:
$model = new appmodelsContactForm;
// "name" - это атрибут модели ContactForm
$model->name = 'example';
echo $model->name;
Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке ArrayAccess и Traversable
в yiibaseModel:
$model = new appmodelsContactForm;
// доступ к атрибутам как к элементам массива
$model['name'] = 'example';
echo $model['name'];
// Модель является обходимой(traversable) с использованием foreach.
foreach ($model as $name => $value) {
echo "$name: $valuen";
}
Определение Атрибутов ¶
По умолчанию, если ваш класс модели расширяется напрямую от yiibaseModel, то все не статичные публичные переменные являются атрибутами. Например, у класса модели ContactForm
, который находится ниже, четыре атрибута: name
, email
, subject
и body
. Модель ContactForm
используется для представления входных данных, полученных из HTML формы.
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
Вы можете переопределить метод yiibaseModel::attributes(), чтобы определять атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например yiidbActiveRecord делает так, возвращая имена столбцов из связанной таблицы базы данных в качестве имён атрибутов. Также может понадобиться переопределить магические методы, такие как __get()
, __set()
для того, чтобы атрибуты могли быть доступны как обычные свойства объекта.
Метки атрибутов ¶
При отображении значений или при получении ввода значений атрибутов, часто требуется отобразить некоторые надписи, связанные с атрибутами. Например, если атрибут назван firstName
, Вы можете отобразить его как First Name
, что является более удобным для пользователя, в тех случаях, когда атрибут отображается конечным пользователям в таких местах, как форма входа и сообщения об ошибках.
Вы можете получить метку атрибута, вызвав yiibaseModel::getAttributeLabel(). Например,
$model = new appmodelsContactForm;
// отобразит "Name"
echo $model->getAttributeLabel('name');
По умолчанию, метки атрибутов автоматически генерируются из названия атрибута. Генерация выполняется методом yiibaseModel::generateAttributeLabel(). Он превращает первую букву каждого слова в верхний регистр, если имена переменных состоят из нескольких слов. Например, username
станет Username
, а firstName
станет First Name
.
Если Вы не хотите использовать автоматически сгенерированные метки, Вы можете переопределить метод yiibaseModel::attributeLabels(), чтобы явно объявить метку атрибута. Например,
namespace appmodels;
use yiibaseModel;
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
может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему.
Модель использует свойство yiibaseModel::$scenario, чтобы отслеживать сценарий, в котором она используется. По умолчанию, модель поддерживает только один сценарий с именем default
. В следующем коде показано два способа установки сценария модели:
// сценарий задается как свойство
$model = new User;
$model->scenario = User::SCENARIO_LOGIN;
// сценарий задается через конфигурацию
$model = new User(['scenario' => User::SCENARIO_LOGIN]);
По умолчанию сценарии, поддерживаемые моделью, определяются правилами валидации объявленными
в модели. Однако Вы можете изменить это поведение путем переопределения метода yiibaseModel::scenarios() как показано ниже:
namespace appmodels;
use yiidbActiveRecord;
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'],
];
}
}
Информация: В приведенном выше и следующих примерах, классы моделей расширяются от yiidbActiveRecord потому, что использование нескольких сценариев обычно происходит от классов Active Record.
Метод scenarios()
возвращает массив, ключами которого являются имена сценариев, а значения — соответствующие активные атрибуты. Активные атрибуты могут быть массово присвоены и подлежат валидации. В приведенном выше примере, атрибуты username
и password
это активные атрибуты сценария login
, а в сценарии register
так же активным атрибутом является email
вместе с username
и password
.
По умолчанию реализация scenarios()
вернёт все найденные сценарии в правилах валидации, задекларированных в методе yiibaseModel::rules(). При переопределении метода scenarios()
, если Вы хотите ввести новые сценарии помимо стандартных, Вы можете написать код на основе следующего примера:
namespace appmodels;
use yiidbActiveRecord;
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
содержит допустимый адрес электронной почты. Если значения нескольких атрибутов не удовлетворяют соответствующим бизнес-правилам, то должны быть показаны соответствующие сообщения об ошибках, чтобы помочь конечному пользователю исправить допущенные ошибки.
Вы можете вызвать yiibaseModel::validate() для проверки полученных данных. Данный метод будет использовать
правила валидации определённые в yiibaseModel::rules() для проверки каждого соответствующего атрибута. Если ошибок не найдено, то возвращается true
, в противном случае возвращается false
, а ошибки содержит свойство yiibaseModel::$errors. Например,
$model = new appmodelsContactForm;
// модель заполнения атрибутов данными, вводимыми пользователем
$model->attributes = Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// все данные верны
} else {
// проверка не удалась: $errors - это массив содержащий сообщения об ошибках
$errors = $model->errors;
}
Объявляем правила валидации связанные с моделью, переопределяем метод yiibaseModel::rules() возврата правил атрибутов модели которые следует удовлетворить. В следующем примере показаны правила проверки объявленные в модели ContactForm
:
public function rules()
{
return [
// name, email, subject и body атрибуты обязательны
[['name', 'email', 'subject', 'body'], 'required'],
// атрибут email должен быть правильным email адресом
['email', 'email'],
];
}
Правило может использоваться для проверки одного или нескольких атрибутов, также и атрибут может быть проверен одним или несколькими правилами. Пожалуйста, обратитесь к разделу Проверка входных значений для более подробной информации о том, как объявлять правила проверки.
Иногда необходимо, чтобы правила применялись только в определенных сценариях. Чтобы это сделать необходимо указать свойство on
в правилах, следующим образом:
public function rules()
{
return [
// username, email и password требуются в сценарии "register"
[['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER],
// username и password требуются в сценарии "login"
[['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN],
];
}
Если не указать свойство on
, то правило применяется во всех сценариях. Правило называется активным правилом если оно может быть применено в текущем сценарии scenario.
Атрибут будет проверяться тогда и только тогда если он является активным атрибутом объявленным в scenarios()
и
связанным с одним или несколькими активными правилами, объявленными в rules()
.
Массовое Присвоение ¶
Массовое присвоение — это удобный способ заполнения модели данными вводимыми пользователем с помощью одной строки кода. Он заполняет атрибуты модели путем присвоения входных данных непосредственно свойству yiibaseModel::$attributes. Следующие два куска кода эквивалентны, они оба пытаются присвоить данные из формы представленные конечными пользователями атрибутам модели ContactForm
. Ясно, что первый код гораздо чище и менее подвержен ошибкам, чем второй:
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm;
$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;
Безопасные Атрибуты ¶
Массовое присвоение применяется только к так называемым безопасным атрибутам, которые являются атрибутами, перечисленными в yiibaseModel::scenarios() в текущем сценарии scenario модели. Например, если модель User
имеет следующий заданный сценарий, в данном случае это сценарий login
, то только username
и password
могут быть массово присвоены. Любые другие атрибуты останутся нетронутыми.
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
Информация: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель
User
имеет атрибутpermission
, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс.
По умолчанию yiibaseModel::scenarios() будет возвращать все сценарии и атрибуты найденные в yiibaseModel::rules(), если не переопределить этот метод, атрибут будет считаться безопасным только в случае, если он участвует в любом из активных правил проверки.
По этой причине существует специальный валидатор с псевдонимом safe
, он предоставляет возможность объявить атрибут безопасным без фактической его проверки. Например, следующие правила определяют, что оба атрибута title
и description
являются безопасными атрибутами.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Небезопасные атрибуты ¶
Как сказано выше, метод yiibaseModel::scenarios() служит двум целям: определения, какие атрибуты должны быть проверены, и определения, какие атрибуты являются безопасными (т.е. не требуют проверки). В некоторых случаях необходимо проверить атрибут не объявляя его безопасным. Вы можете сделать это с помощью префикса восклицательный знак !
в имени атрибута при объявлении его в scenarios()
как атрибут secret
в следующем примере:
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
];
}
Когда модель будет присутствовать в сценарии login
, то все три эти атрибута будут проверены. Однако, только атрибуты username
и password
могут быть массово присвоены. Назначить входное значение атрибуту secret
нужно явно следующим образом,
$model->secret = $secret;
Экспорт Данных ¶
Часто нужно экспортировать модели в различные форматы. Например, может потребоваться преобразовать коллекцию моделей в JSON или Excel формат. Процесс экспорта может быть разбит на два самостоятельных шага. На первом этапе модели преобразуются в массивы; на втором этапе массивы преобразуются в целевые форматы. Вы можете сосредоточиться только на первом шаге потому, что второй шаг может быть достигнут путем универсального инструмента форматирования данных, такого как yiiwebJsonResponseFormatter.
Самый простой способ преобразования модели в массив — использовать свойство yiibaseModel::$attributes.
Например
$post = appmodelsPost::findOne(100);
$array = $post->attributes;
По умолчанию свойство yiibaseModel::$attributes возвращает значения всех атрибутов объявленных в yiibaseModel::attributes().
Более гибкий и мощный способ конвертирования модели в массив — использовать метод yiibaseModel::toArray(). Его поведение по умолчанию такое же как и у yiibaseModel::$attributes. Тем не менее, он позволяет выбрать, какие элементы данных, называемые полями, поставить в результирующий массив и как они должны быть отформатированы. На самом деле, этот способ экспорта моделей по умолчанию применяется при разработке в RESTful Web service, как описано в Response Formatting.
Поля ¶
Поле — это просто именованный элемент в массиве, который может быть получен вызовом метода yiibaseModel::toArray() модели.
По умолчанию имена полей эквивалентны именам атрибутов. Однако, это поведение можно изменить, переопределив методы
fields() и/или extraFields(). Оба метода должны возвращать список определенных полей. Поля определённые fields()
являются полями по умолчанию, это означает, что toArray()
будет возвращать эти поля по умолчанию. Метод extraFields()
определяет дополнительно доступные поля, которые также могут быть возвращены toArray()
так много, как Вы укажите их через параметр $expand
. Например, следующий код будет возвращать все поля определённые в fields()
, а также поля prettyName
и fullAddress
, если они определены в extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Вы можете переопределить fields()
чтобы добавить, удалить, переименовать или переопределить поля. Возвращаемым значением fields()
должен быть массив. Ключами массива являются имена полей, а значениями — соответствующие определения полей, которые могут быть либо именами свойств/атрибутов, либо анонимными функциями, возвращающими соответствующие значения полей. В частном случае, когда имя поля совпадает с именем его атрибута, возможно опустить ключ массива. Например,
// использовать явное перечисление всех полей, лучше всего тогда, когда вы хотите убедиться,
// что изменения в вашей таблице базы данных или атрибуте модели не вызывают изменение вашего поля
// (для поддержания обратной совместимости API интерфейса).
public function fields()
{
return [
// здесь имя поля совпадает с именем атрибута
'id',
// здесь имя поля - "email", соответствующее ему имя атрибута - "email_address"
'email' => 'email_address',
// здесь имя поля - "name", а значение определяется обратным вызовом PHP
'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;
}
Внимание: по умолчанию все атрибуты модели будут включены в экспортируемый массив, вы должны проверить ваши данные и убедиться, что они не содержат конфиденциальной информации. Если такая информация присутствует, вы должны переопределить
fields()
и отфильтровать поля. В приведенном выше примере мы выбираем и отфильтровываемauth_key
,password_hash
иpassword_reset_token
.
Лучшие практические методики разработки моделей ¶
Модели являются центральным местом представления бизнес-данных, правил и логики. Они часто повторно используются в разных местах. В хорошо спроектированном приложении, модели, как правило, намного больше, чем контроллеры.
В целом, модели
- могут содержать атрибуты для представления бизнес-данных;
- могут содержать правила проверки для обеспечения целостности и достоверности данных;
- могут содержать методы с реализацией бизнес-логики;
- не следует напрямую задавать запрос на доступ, либо сессии, либо любые другие данные об окружающей среде. Эти данные должны быть введены контроллерами в модели;
- следует избегать встраивания HTML или другого отображаемого кода — это лучше делать в видах;
- избегайте слишком большого количества сценариев в одной модели.
Рекомендации выше обычно учитываются при разработке больших сложных систем. В таких системах, модели могут быть очень большими, в связи с тем, что они используются во многих местах и поэтому могут содержать множество наборов правил и бизнес-логики. Это часто заканчивается кошмаром при поддержании кода модели, поскольку одним касанием кода можно повлиять на несколько разных мест. Чтобы сделать код модели более легким в обслуживании, Вы можете предпринять следующую стратегию:
- Определить набор базовых классов моделей, которые являются общими для разных приложений или модулей. Эти классы моделей должны содержать минимальный набор правил и логики, которые являются общими среди всех используемых приложений или модулей.
- В каждом приложении или модуле в котором используется модель, определить конкретный класс модели (или классы моделей), отходящий от соответствующего базового класса модели. Конкретный класс модели должен содержать правила и логику, которые являются специфическими для данного приложения или модуля.
Например, в шаблоне приложения advanced, Вы можете определить базовым классом модели commonmodelsPost
. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели frontendmodelsPost
, который расширяется от commonmodelsPost
. И аналогичным образом для backend приложения, Вы определяете backendmodelsPost
. С помощью такой стратегии, можно быть уверенным, что код в frontendmodelsPost
используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение.
Models are part of the MVC architecture.
They are objects representing business data, rules and logic.
You can create model classes by extending yiibaseModel or its child classes. The base class
yiibaseModel supports many useful features:
- Attributes: represent the business data and can be accessed like normal object properties
or array elements; - Attribute labels: specify the display labels for attributes;
- Massive assignment: supports populating multiple attributes in a single step;
- Validation rules: ensures input data based on the declared validation rules;
- Data Exporting: allows model data to be exported in terms of arrays with customizable formats.
The Model
class is also the base class for more advanced models, such as Active Record.
Please refer to the relevant documentation for more details about these advanced models.
Info: You are not required to base your model classes on yiibaseModel. However, because there are many Yii
components built to support yiibaseModel, it is usually the preferable base class for a model.
Attributes ¶
Models represent business data in terms of attributes. Each attribute is like a publicly accessible property
of a model. The method yiibaseModel::attributes() specifies what attributes a model class has.
You can access an attribute like accessing a normal object property:
$model = new appmodelsContactForm;
// "name" is an attribute of ContactForm
$model->name = 'example';
echo $model->name;
You can also access attributes like accessing array elements, thanks to the support for
ArrayAccess and Traversable
by yiibaseModel:
$model = new appmodelsContactForm;
// accessing attributes like array elements
$model['name'] = 'example';
echo $model['name'];
// Model is traversable using foreach.
foreach ($model as $name => $value) {
echo "$name: $valuen";
}
Defining Attributes ¶
By default, if your model class extends directly from yiibaseModel, all its non-static public member
variables are attributes. For example, the ContactForm
model class below has four attributes: name
, email
,
subject
and body
. The ContactForm
model is used to represent the input data received from an HTML form.
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
You may override yiibaseModel::attributes() to define attributes in a different way. The method should
return the names of the attributes in a model. For example, yiidbActiveRecord does so by returning
the column names of the associated database table as its attribute names. Note that you may also need to
override the magic methods such as __get()
, __set()
so that the attributes can be accessed like
normal object properties.
Attribute Labels ¶
When displaying values or getting input for attributes, you often need to display some labels associated
with attributes. For example, given an attribute named firstName
, you may want to display a label First Name
which is more user-friendly when displayed to end users in places such as form inputs and error messages.
You can get the label of an attribute by calling yiibaseModel::getAttributeLabel(). For example,
$model = new appmodelsContactForm;
// displays "Name"
echo $model->getAttributeLabel('name');
By default, attribute labels are automatically generated from attribute names. The generation is done by
the method yiibaseModel::generateAttributeLabel(). It will turn camel-case variable names into
multiple words with the first letter in each word in upper case. For example, username
becomes Username
,
and firstName
becomes First Name
.
If you do not want to use automatically generated labels, you may override yiibaseModel::attributeLabels()
to explicitly declare attribute labels. For example,
namespace appmodels;
use yiibaseModel;
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',
];
}
}
For applications supporting multiple languages, you may want to translate attribute labels. This can be done
in the attributeLabels() method as well, like the following:
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'),
];
}
You may even conditionally define attribute labels. For example, based on the scenario the model
is being used in, you may return different labels for the same attribute.
Info: Strictly speaking, attribute labels are part of views. But declaring labels
in models is often very convenient and can result in very clean and reusable code.
Scenarios ¶
A model may be used in different scenarios. For example, a User
model may be used to collect user login inputs,
but it may also be used for the user registration purpose. In different scenarios, a model may use different
business rules and logic. For example, the email
attribute may be required during user registration,
but not so during user login.
A model uses the yiibaseModel::$scenario property to keep track of the scenario it is being used in.
By default, a model supports only a single scenario named default
. The following code shows two ways of
setting the scenario of a model:
// 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]);
By default, the scenarios supported by a model are determined by the validation rules declared
in the model. However, you can customize this behavior by overriding the yiibaseModel::scenarios() method,
like the following:
namespace appmodels;
use yiidbActiveRecord;
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'],
];
}
}
Info: In the above and following examples, the model classes are extending from yiidbActiveRecord
because the usage of multiple scenarios usually happens to Active Record classes.
The scenarios()
method returns an array whose keys are the scenario names and values the corresponding
active attributes. An active attribute can be massively assigned and is subject
to validation. In the above example, the username
and password
attributes are active
in the login
scenario; while in the register
scenario, email
is also active besides username
and password
.
The default implementation of scenarios()
will return all scenarios found in the validation rule declaration
method yiibaseModel::rules(). When overriding scenarios()
, if you want to introduce new scenarios
in addition to the default ones, you may write code like the following:
namespace appmodels;
use yiidbActiveRecord;
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;
}
}
The scenario feature is primarily used by validation and massive attribute assignment.
You can, however, use it for other purposes. For example, you may declare attribute labels
differently based on the current scenario.
Validation Rules ¶
When the data for a model is received from end users, it should be validated to make sure it satisfies
certain rules (called validation rules, also known as business rules). For example, given a ContactForm
model,
you may want to make sure all attributes are not empty and the email
attribute contains a valid email address.
If the values for some attributes do not satisfy the corresponding business rules, appropriate error messages
should be displayed to help the user to fix the errors.
You may call yiibaseModel::validate() to validate the received data. The method will use
the validation rules declared in yiibaseModel::rules() to validate every relevant attribute. If no error
is found, it will return true
. Otherwise, it will keep the errors in the yiibaseModel::$errors property
and return false
. For example,
$model = new appmodelsContactForm;
// 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;
}
To declare validation rules associated with a model, override the yiibaseModel::rules() method by returning
the rules that the model attributes should satisfy. The following example shows the validation rules declared
for the ContactForm
model:
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'],
];
}
A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules.
Please refer to the Validating Input section for more details on how to declare
validation rules.
Sometimes, you may want a rule to be applied only in certain scenarios. To do so, you can
specify the on
property of a rule, like the following:
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],
];
}
If you do not specify the on
property, the rule would be applied in all scenarios. A rule is called
an active rule if it can be applied in the current scenario.
An attribute will be validated if and only if it is an active attribute declared in scenarios()
and
is associated with one or multiple active rules declared in rules()
.
Massive Assignment ¶
Massive assignment is a convenient way of populating a model with user inputs using a single line of code.
It populates the attributes of a model by assigning the input data directly to the yiibaseModel::$attributes
property. The following two pieces of code are equivalent, both trying to assign the form data submitted by end users
to the attributes of the ContactForm
model. Clearly, the former, which uses massive assignment, is much cleaner
and less error prone than the latter:
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm;
$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;
Safe Attributes ¶
Massive assignment only applies to the so-called safe attributes which are the attributes listed in
yiibaseModel::scenarios() for the current scenario of a model.
For example, if the User
model has the following scenario declaration, then when the current scenario
is login
, only the username
and password
can be massively assigned. Any other attributes will
be kept untouched.
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
Info: The reason that massive assignment only applies to safe attributes is because you want to
control which attributes can be modified by end user data. For example, if theUser
model
has apermission
attribute which determines the permission assigned to the user, you would
like this attribute to be modifiable by administrators through a backend interface only.
Because the default implementation of yiibaseModel::scenarios() will return all scenarios and attributes
found in yiibaseModel::rules(), if you do not override this method, it means an attribute is safe as long
as it appears in one of the active validation rules.
For this reason, a special validator aliased safe
is provided so that you can declare an attribute
to be safe without actually validating it. For example, the following rules declare that both title
and description
are safe attributes.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Unsafe Attributes ¶
As described above, the yiibaseModel::scenarios() method serves for two purposes: determining which attributes
should be validated, and determining which attributes are safe. In some rare cases, you may want to validate
an attribute but do not want to mark it safe. You can do so by prefixing an exclamation mark !
to the attribute
name when declaring it in scenarios()
, like the secret
attribute in the following:
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
];
}
When the model is in the login
scenario, all three attributes will be validated. However, only the username
and password
attributes can be massively assigned. To assign an input value to the secret
attribute, you
have to do it explicitly as follows,
$model->secret = $secret;
The same can be done in rules()
method:
public function rules()
{
return [
[['username', 'password', '!secret'], 'required', 'on' => 'login']
];
}
In this case attributes username
, password
and secret
are required, but secret
must be assigned explicitly.
Data Exporting ¶
Models often need to be exported in different formats. For example, you may want to convert a collection of
models into JSON or Excel format. The exporting process can be broken down into two independent steps:
- models are converted into arrays;
- the arrays are converted into target formats.
You may just focus on the first step, because the second step can be achieved by generic
data formatters, such as yiiwebJsonResponseFormatter.
The simplest way of converting a model into an array is to use the yiibaseModel::$attributes property.
For example,
$post = appmodelsPost::findOne(100);
$array = $post->attributes;
By default, the yiibaseModel::$attributes property will return the values of all attributes
declared in yiibaseModel::attributes().
A more flexible and powerful way of converting a model into an array is to use the yiibaseModel::toArray()
method. Its default behavior is the same as that of yiibaseModel::$attributes. However, it allows you
to choose which data items, called fields, to be put in the resulting array and how they should be formatted.
In fact, it is the default way of exporting models in RESTful Web service development, as described in
the Response Formatting.
Fields ¶
A field is simply a named element in the array that is obtained by calling the yiibaseModel::toArray() method
of a model.
By default, field names are equivalent to attribute names. However, you can change this behavior by overriding
the fields() and/or extraFields() methods. Both methods
should return a list of field definitions. The fields defined by fields()
are default fields, meaning that
toArray()
will return these fields by default. The extraFields()
method defines additionally available fields
which can also be returned by toArray()
as long as you specify them via the $expand
parameter. For example,
the following code will return all fields defined in fields()
and the prettyName
and fullAddress
fields
if they are defined in extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
You can override fields()
to add, remove, rename or redefine fields. The return value of fields()
should be an array. The array keys are the field names, and the array values are the corresponding
field definitions which can be either property/attribute names or anonymous functions returning the
corresponding field values. In the special case when a field name is the same as its defining attribute
name, you can omit the array key. For example,
// 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 exclude 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;
}
Warning: Because by default all attributes of a model will be included in the exported array, you should
examine your data to make sure they do not contain sensitive information. If there is such information,
you should overridefields()
to filter them out. In the above example, we choose
to filter outauth_key
,password_hash
andpassword_reset_token
.
Best Practices ¶
Models are the central places to represent business data, rules and logic. They often need to be reused
in different places. In a well-designed application, models are usually much fatter than
controllers.
In summary, models
- may contain attributes to represent business data;
- may contain validation rules to ensure the data validity and integrity;
- may contain methods implementing business logic;
- should NOT directly access request, session, or any other environmental data. These data should be injected
by controllers into models; - should avoid embedding HTML or other presentational code — this is better done in views;
- avoid having too many scenarios in a single model.
You may usually consider the last recommendation above when you are developing large complex systems.
In these systems, models could be very fat because they are used in many places and may thus contain many sets
of rules and business logic. This often ends up in a nightmare in maintaining the model code
because a single touch of the code could affect several different places. To make the model code more maintainable,
you may take the following strategy:
- Define a set of base model classes that are shared by different applications or
modules. These model classes should contain minimal sets of rules and logic that
are common among all their usages. - In each application or module that uses a model,
define a concrete model class by extending from the corresponding base model class. The concrete model classes
should contain rules and logic that are specific for that application or module.
For example, in the Advanced Project Template, you may define a base model
class commonmodelsPost
. Then for the front end application, you define and use a concrete model class
frontendmodelsPost
which extends from commonmodelsPost
. And similarly for the back end application,
you define backendmodelsPost
. With this strategy, you will be sure that the code in frontendmodelsPost
is only specific to the front end application, and if you make any change to it, you do not need to worry if
the change may break the back end application.
Модели
Модели являются частью архитектуры MVC (Модель-Вид-Контроллер). Они представляют собой объекты бизнес данных, правил и логики.
Вы можете создавать классы моделей путём расширения класса [[yiibaseModel]] или его дочерних классов. Базовый класс [[yiibaseModel]] поддерживает много полезных функций:
- Атрибуты: представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов: задают отображение атрибута;
- Массовое присвоение: поддержка заполнения нескольких атрибутов в один шаг;
- Правила проверки: обеспечивают ввод данных на основе заявленных правил проверки;
- Экспорт Данных: разрешает данным модели быть экспортированными в массивы с настройкой форматов.
Класс Model
также является базовым классом для многих расширенных моделей, таких как Active Record. Пожалуйста, обратитесь к соответствующей документации для более подробной информации об этих расширенных моделях.
Для справки: Вы не обязаны основывать свои классы моделей на [[yiibaseModel]]. Однако, поскольку в yii есть много компонентов, созданных для поддержки [[yiibaseModel]], обычно так делать предпочтительнее для базового класса модели.
Атрибуты
Модели предоставляют рабочие данные в терминах атрибутах. Каждый атрибут представляет собой публично доступное свойство модели. Метод [[yiibaseModel::attributes()]] определяет какие атрибуты имеет класс модели.
Вы можете получить доступ к атрибуту как к обычному свойству объекта:
$model = new appmodelsContactForm;
// "name" - это атрибут модели ContactForm
$model->name = 'example';
echo $model->name;
Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке ArrayAccess и ArrayIterator
в [[yiibaseModel]]:
$model = new appmodelsContactForm;
// доступ к атрибутам как к элементам массива
$model['name'] = 'example';
echo $model['name'];
// перебор атрибутов
foreach ($model as $name => $value) {
echo "$name: $valuen";
}
Определение Атрибутов
По умолчанию, если ваш класс модели расширяется напрямую от [[yiibaseModel]], то все не статичные публичные переменные являются атрибутами. Например, у класса модели ContactForm
, который находится ниже, четыре атрибута: name
, email
, subject
и body
. Модель ContactForm
используется для представления входных данных, полученных из HTML формы.
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
Вы можете переопределить метод [[yiibaseModel::attributes()]], чтобы определять атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например [[yiidbActiveRecord]] делает так, возвращая имена столбцов из связанной таблицы базы данных в качестве имён атрибутов. Также может понадобиться переопределить магические методы, такие как __get()
, __set()
для того, что бы атрибуты могли быть доступны как обычные свойства объекта.
Метки атрибутов
При отображении значений или при получении ввода значений атрибутов, часто требуется отобразить некоторые надписи, связанные с атрибутами. Например, если атрибут назван firstName
, Вы можете отобразить его как First Name
, что является более удобным для пользователя, в тех случаях, когда атрибут отображается конечным пользователям в таких местах, как форма входа и сообщения об ошибках.
Вы можете получить метку атрибута, вызвав [[yiibaseModel::getAttributeLabel()]]. Например,
$model = new appmodelsContactForm;
// отобразит "Name"
echo $model->getAttributeLabel('name');
По умолчанию, метки атрибутов автоматически генерируются из названия атрибута. Генерация выполняется методом [[yiibaseModel::generateAttributeLabel()]]. Он превращает первую букву каждого слова в верхний регистр, если имена переменных состоят из нескольких слов. Например, username
станет Username
, а firstName
станет First Name
.
Если Вы не хотите использовать автоматически сгенерированные метки, Вы можете переопределить метод [[yiibaseModel::attributeLabels()]], чтобы явно объявить метку атрибута. Например,
namespace appmodels;
use yiibaseModel;
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',
];
}
}
Для приложений поддерживающих мультиязычность, Вы можете перевести метки атрибутов. Это можно сделать в методе [[yiibaseModel::attributeLabels()|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
может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему.
Модель использует свойство [[yiibaseModel::scenario]], чтобы отслеживать сценарий, в котором она используется. По умолчанию, модель поддерживает только один сценарий с именем default
. В следующем коде показано два способа установки сценария модели:
// сценарий задается как свойство
$model = new User;
$model->scenario = 'login';
// сценарий задается через конфигурацию
$model = new User(['scenario' => 'login']);
По умолчанию сценарии, поддерживаемые моделью, определяются правилами валидации объявленными
в модели. Однако, Вы можете изменить это поведение путем переопределения метода [[yiibaseModel::scenarios()]] как показано ниже:
namespace appmodels;
use yiidbActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
}
Для справки: В приведенном выше и следующих примерах, классы моделей расширяются от [[yiidbActiveRecord]] потому, что использование нескольких сценариев обычно происходит от классов Active Record.
Метод scenarios()
возвращает массив, ключами которого являются имена сценариев, а значения — соответствующие активные атрибуты. Активные атрибуты могут быть массово присвоены и подлежат валидации. В приведенном выше примере, атрибуты username
и password
это активные атрибуты сценария login
, а в сценарии register
так же активным атрибутом является email
вместе с username
и password
.
По умолчанию реализация scenarios()
вернёт все найденные сценарии в правилах валидации задекларированных в методе [[yiibaseModel::rules()]]. При переопределении метода scenarios()
, если Вы хотите ввести новые сценарии помимо стандартных, Вы можете написать код на основе следующего примера:
namespace appmodels;
use yiidbActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['username', 'password'];
$scenarios['register'] = ['username', 'email', 'password'];
return $scenarios;
}
}
Возможности сценариев в основном используются валидацией и массовым присвоением атрибутов. Однако, Вы можете использовать их и для других целей. Например, Вы можете различным образом объявлять метки атрибутов на основе текущего сценария.
Правила валидации
Когда данные модели, получены от конечных пользователей, они должны быть проверены, для того чтобы убедиться, что данные удовлетворяют определенным правилам (так называемым правилам валидации также известными как бизнес-правила). Например, дана модель ContactForm
, возможно Вы захотите убедиться, что все атрибуты являются не пустыми значениями, а атрибут email
содержит допустимый адрес электронной почты. Если значения нескольких атрибутов не удовлетворяют соответствующим бизнес-правилам, то должны быть показаны соответствующие сообщения об ошибках, чтобы помочь конечному пользователю исправить допущенные ошибки.
Вы можете вызвать [[yiibaseModel::validate()]] для проверки полученных данных. Данный метод будет использовать
правила валидации определённые в [[yiibaseModel::rules()]] для проверки каждого соответствующего атрибута. Если ошибок не найдено, то возвращается True, в противном случае возвращается false, а ошибки содержит свойство [[yiibaseModel::errors]]. Например,
$model = new appmodelsContactForm;
// модель заполнения атрибутов данными, вводимыми пользователем
$model->attributes = Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// все данные верны
} else {
// проверка не удалась: $errors - это массив содержащий сообщения об ошибках
$errors = $model->errors;
}
Объявляем правила валидации связанные с моделью, переопределяем метод [[yiibaseModel::rules()]] возврата правил атрибутов модели которые следует удовлетворить. В следующем примере показаны правила проверки объявленные в модели ContactForm
:
public function rules()
{
return [
// name, email, subject и body атрибуты обязательны
[['name', 'email', 'subject', 'body'], 'required'],
// атрибут email должен быть правильным email адресом
['email', 'email'],
];
}
Правило может использоваться для проверки одного или нескольких атрибутов, также и атрибут может быть проверен одним или несколькими правилами. Пожалуйста, обратитесь к разделу Проверка входных значений для более подробной информации о том, как объявлять правила проверки.
Иногда необходимо, чтобы правила применялись только в определенных сценариях. Чтобы это сделать необходимо указать свойство on
в правилах, следующим образом:
public function rules()
{
return [
// username, email и password требуются в сценарии "register"
[['username', 'email', 'password'], 'required', 'on' => 'register'],
// username и password требуются в сценарии "login"
[['username', 'password'], 'required', 'on' => 'login'],
];
}
Если не указать свойство on
, то правило применяется во всех сценариях. Правило называется активным правилом если оно может быть применено в текущем сценарии [[yiibaseModel::scenario|scenario]].
Атрибут будет проверяться тогда и только тогда если он является активным атрибутом объявленным в scenarios()
и
связаным с одним или несколькими активными правилами, объявленными в rules()
.
Массовое Присвоение
Массовое присвоение — это удобный способ заполнения модели данными вводимыми пользователем с помощью одной строки кода. Он заполняет атрибуты модели путем присвоения входных данных непосредственно свойству [[yiibaseModel::$attributes]]. Следующие два куска кода эквивалентны, они оба пытаются присвоить данные из формы представленные конечными пользователями атрибутам модели ContactForm
. Ясно, что первый код гораздо чище и менее подвержен ошибкам, чем второй:
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm;
$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;
Безопасные Атрибуты
Массовое присвоение применяется только к так называемым безопасным атрибутам, которые являются атрибутами, перечисленными в [[yiibaseModel::scenarios()]] в текущем сценарии [[yiibaseModel::scenario|scenario]] модели. Например, если модель User
имеет следующий заданный сценарий, в данном случае это сценарий login
, то только username
и password
могут быть массово присвоены. Любые другие атрибуты останутся нетронутыми.
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
Для справки: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель
User
имеет атрибутpermission
, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс.
По умолчанию реализация [[yiibaseModel::scenarios()]] будет возвращать все сценарии и атрибуты найденные в [[yiibaseModel::rules()]], если не переопределить этот метод, это будет означать, что атрибуты являются безопасными до тех пор пока они не появятся в одном из активных правил проверки.
По этой причине существует специальный валидатор с псевдонимом safe
, он предоставляет возможность объявить атрибут безопасным без фактической его проверки. Например, следующие правила определяют, что оба атрибута title
и description
являются безопасными атрибутами.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Небезопасные атрибуты
Как сказано выше, метод [[yiibaseModel::scenarios()]] служит двум целям: определения, какие атрибуты должны быть проверены, и определения, какие атрибуты являются безопасными (т.е. не требуют проверки). В некоторых случаях необходимо проверить атрибут не объявляя его безопасным. Вы можете сделать это с помощью префикса восклицательный знак !
в имени атрибута при объявлении его в scenarios()
как атрибут secret
в следующем примере:
public function scenarios()
{
return [
'login' => ['username', 'password', '!secret'],
];
}
Когда модель будет присутствовать в сценарии login
, то все три эти атрибута будут проверены. Однако, только атрибуты username
и password
могут быть массово присвоены. Назначить входное значение атрибуту secret
нужно явно следующим образом,
$model->secret = $secret;
Экспорт Данных
Часто нужно экспортировать модели в различные форматы. Например, может потребоваться преобразовать коллекцию моделей в JSON или Excel формат. Процесс экспорта может быть разбит на два самостоятельных шага. На первом этапе модели преобразуются в массивы; на втором этапе массивы преобразуются в целевые форматы. Вы можете сосредоточиться только на первом шаге потому, что второй шаг может быть достигнут путем универсального инструмента форматирования данных, такого как [[yiiwebJsonResponseFormatter]].
Самый простой способ преобразования модели в массив — использовать свойство [[yiibaseModel::$attributes]].
Например,
$post = appmodelsPost::findOne(100);
$array = $post->attributes;
По умолчанию, свойство [[yiibaseModel::$attributes]] возвращает значения всех атрибутов объявленных в [[yiibaseModel::attributes()]].
Более гибкий и мощный способ конвертирования модели в массив — использовать метод [[yiibaseModel::toArray()]]. Его поведение по умолчанию такое же как и у [[yiibaseModel::$attributes]]. Тем не менее, он позволяет выбрать, какие элементы данных, называемые полями, поставить в результирующий массив и как они должны быть отформатированы. На самом деле, этот способ экспорта моделей по умолчанию применяется при разработке в RESTful Web service, как описано в Response Formatting.
Поля
Поле — это просто именованный элемент в массиве, который может быть получен вызовом метода [[yiibaseModel::toArray()]] модели.
По умолчанию имена полей эквивалентны именам атрибутов. Однако, это поведение можно изменить, переопределив методы
[[yiibaseModel::fields()|fields()]] и/или [[yiibaseModel::extraFields()|extraFields()]]. Оба метода должны возвращать список определенных полей. Поля определённые fields()
являются полями по умолчанию, это означает, что toArray()
будет возвращать эти поля по умолчанию. Метод extraFields()
определяет дополнительно доступные поля, которые также могут быть возвращены toArray()
так много, как Вы укажите их через параметр $expand
. Например, следующий код будет возвращать все поля определённые в fields()
, а также поля prettyName
и fullAddress
, если они определены в extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Вы можете переопределить fields()
чтобы добавить, удалить, переименовать или переопределить поля. Возвращаемым значением fields()
должен быть массив. Ключами массива являются имена полей, а значениями — соответствующие определения полей, которые могут быть либо именами свойств/атрибутов, либо анонимными функциями, возвращающими соответствующие значения полей. В частном случае, когда имя поля совпадает с именем его атрибута, возможно опустить ключ массива. Например,
// использовать явное перечисление всех полей, лучше всего тогда, когда вы хотите убедиться,
// что изменения в вашей таблице базы данных или атрибуте модели не вызывают изменение вашего поля
// (для поддержания обратной совместимости API интерфейса).
public function fields()
{
return [
// здесь имя поля совпадает с именем атрибута
'id',
// здесь имя поля - "email", соответствующее ему имя атрибута - "email_address"
'email' => 'email_address',
// здесь имя поля - "name", а значение определяется обратным вызовом PHP
'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;
}
Внимание: по умолчанию все атрибуты модели будут включены в экспортируемый массив, вы должны проверить ваши данные и убедиться, что они не содержат конфиденциальной информации. Если такая информация присутствует, вы должны переопределить
fields()
и отфильтровать поля. В приведенном выше примере мы выбираем и отфильтровываемauth_key
,password_hash
иpassword_reset_token
.
Лучшие практические методики разработки моделей
Модели являются центральным местом представления бизнес-данных, правил и логики. Они часто повторно используются в разных местах. В хорошо спроектированном приложении, модели, как правило, намного больше, чем контроллеры.
В целом, модели
- могут содержать атрибуты для представления бизнес-данных;
- могут содержать правила проверки для обеспечения целостности и достоверности данных;
- могут содержать методы с реализацией бизнес-логики;
- не следует напрямую задавать запрос на доступ, либо сессии, либо любые другие данные об окружающей среде. Эти данные должны быть введены контроллерами в модели;
- следует избегать встраивания HTML или другого отображаемого кода — это лучше делать в видах;
- избегайте слишком большого количества сценариев в одной модели.
Рекомендации выше обычно учитываются при разработке больших сложных систем. В таких системах, модели могут быть очень большими, в связи стем, что они используются во многих местах и поэтому могут содержать множество наборов правил и бизнес-логики. Это часто заканчивается кошмаром при поддержании кода модели, поскольку одним касанием кода можно повлиять на несколько разных мест. Чтобы сделать код модели более легким в обслуживании, Вы можете предпринять следующую стратегию:
- Определить набор базовых классов моделей, которые являются общими для разных приложений или модулей. Эти классы моделей должны содержать минимальный набор правил и логики, которые являются общими среди всех используемых приложений или модулей.
- В каждом приложении или модуле в котором используется модель, определить конкретный класс модели (или классы моделей), отходящий от соответствующего базового класса модели. Конкретный класс модели должен содержать правила и логику, которые являются специфическими для данного приложения или модуля.
Например, в Дополнительном Шаблоне Проекта, Вы можете определить базовым классом модели commonmodelsPost
. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели frontendmodelsPost
, который расширяется от commonmodelsPost
. И аналогичным образом для backend приложения, Вы определяете backendmodelsPost
. С помощью такой стратегии, можно быть уверенным, что код в frontendmodelsPost
используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение.
В Yii2 есть возможность добавления сценариев в модели, например для create()
и update()
. В этой записи, я покажу как создать сценарий и использовать одну и туже модель для валидации одной и той же формы в нескольких местах.
Где это может пригодиться?
Например, вы используете Yii2 advanced шаблон для разработки веб-приложения у которого должен быть админ центр для управления тем, что добавляют пользователя из frontend.
Представим, что вы хотите добавить возможность создания блог-записей на стороне обычного пользователя (frontend) и администратора (backend). Но скорее всего в backend будет больше полей для добавления, так как администратор должен иметь возможность добавлять и изменять все что угодно.
Если не использовать сценарии для модели, то в таком случае должно использоваться две модели. Одна в backend/models
, а другая в frontend/models
.
Но зачем себе утруждать жизнь, если можно создать сценарии и использовать одну модель, которая будет находиться в common/models
. Удобно, не правда ли?
Создаем ActiveRecord модель и правила
Создали обычную модель, назвали её Post
, она использовать методы из ActiveRecord
, которая в свою очередь помогает работать с базой данных.
С помощью метода tableName()
указываем название таблицы для этого класса, в нашем случае — это posts
.
Далее указываем правила для модели с помощью метода rules()
и метод authorExists()
помогает проверить существует id
автора в таблице пользователей.
В принципе, обычная модель, ничего сложного.
<?php namespace commonmodels; use yiidbActiveRecord; use commonmodelsUser; /** * This is the Post model */ class Post extends ActiveRecord { /** * @inheritdoc */ public static function tableName() { return 'posts'; } /** * @inheritdoc */ public function rules() { return [ ['image', 'safe'], [['post_title', 'post_body', 'author_id'], 'required'], [['post_title', 'type'], 'string', 'max' => 80], [['post_body'], 'string', 'max' => 3000], [['author_id'], 'integer'], ['author_id', 'authorExists'] ]; } /** * Проверка на существование пользователь по User модели * @return mixed */ public function authorExists($attribute, $params) { if( !$this->hasErrors() ) { $userExists = User::find()->where(['id' => $this->author_id])->exists(); if( !$userExists ) { $this->addError($attribute, 'Ошибка, такого пользователя не существует.'); } } } }
Создаем сценарий
Теперь давайте разберемся со сценариями.
Сценарии я обозначил с помощью const
, они идут как правило самыми первыми, перед всеми методами и значениями модели.
Название может быть любое, но лучше всего начинать с SCENARIO_
и далее само название.
И конечно же нужно перезаписать метод scenarios()
, внутри которого мы добавили два новых значения — SCENARIO_ADMIN
и SCENARIO_USER
.
SCENARIO_ADMIN
будет использоваться в админке, а SCENARIO_USER
для обычного пользователя.
Разница в том, чтобы при создании поста, обычный пользователь не может изменить id
автора, потому что это значение будет поставлено за него исходя из значения текущий сессии, которая храниться в Yii::$app->user->identity
. (если конечно вы это используете, а лучше это делать :)).
Админ будет иметь возможность изменить автора поста, потому что теперь вы можете изменить сценарий и тем самым валидация полей будет происходит по разным правилам.
Теперь давайте посмотрим, что изменилось в классе после добавления всего что описано выше.
<?php namespace commonmodels; use yiidbActiveRecord; use commonmodelsUser; /** * This is the Post model */ class Post extends ActiveRecord { const SCENARIO_ADMIN = 'admin'; const SCENARIO_USER = 'user'; /** * @inheritdoc */ public static function tableName() { return 'posts'; } /** * @inheritdoc */ public function rules() { return [ ['image', 'safe'], [['post_title', 'post_body', 'author_id'], 'required'], [['post_title', 'type'], 'string', 'max' => 80], [['post_body'], 'string', 'max' => 3000], [['author_id'], 'integer'], ['author_id', 'authorExists'] ]; } /** * Сценарии * @return array */ public function scenarios() { $scenarios = parent::scenarios(); $scenarios[static::SCENARIO_ADMIN] = ['post_title', 'post_body', 'author_id']; $scenarios[static::SCENARIO_USER] = ['post_title', 'post_body']; return $scenarios; } /** * Проверка на существование пользователь по User модели * @return mixed */ public function authorExists($attribute, $params) { if( !$this->hasErrors() ) { $userExists = User::find()->where(['id' => $this->author_id])->exists(); if( !$userExists ) { $this->addError($attribute, 'Ошибка, такого пользователя не существует.'); } } } }
Хочу еще обратить внимание на scenarios()
В этой сценарии вы не полностью перезаписываем его, потому что там есть SCENARIO_DEFAULT
, который используется как стандартный для валидации всех правил модели.
И чтобы его не сносить, вы используем parent::scenarios()
, то вызываем стандартный сценарий из ActiveRecord
и уже к нему добавляем новые.
Для наглядности, я вытащил этот метод из класса выше, чтобы вы могли посмотреть на этой еще раз:
/** * Сценарии * @return array */ public function scenarios() { $scenarios = parent::scenarios(); $scenarios[static::SCENARIO_ADMIN] = ['post_title', 'post_body', 'author_id']; $scenarios[static::SCENARIO_USER] = ['post_title', 'post_body']; return $scenarios; }
Как использовать сценарии в action’ах
Для создания нового поста, в action create вы можете добавить сценарий с помощью конструктора, то есть в следующей форме: new Post(['scenario' => Post::SCENARIO_USER])
.
Если вы изменяете пост, то это так же можно сделать добавив параметр $model->scenario = Post::SCENARIO_USER
.
Пример кода контроллера (controller) ниже:
<?php use yiiwebController; PostController extends Controller { ... public function actionCreate() { $model = new Post(['scenario' => Post::SCENARIO_USER]); ... } ... public function actionUpdate($id) { $model = $this->findModel($id); $model->scenario = Post::SCENARIO_USER; ... } protected function findModel($id) { if (($model = Post::findOne($id)) !== null) { return $model; } else { throw new NotFoundHttpException('Запрашиваемая страница не найдена'); } } }
https://stackoverflow.com/questions/30886437/yii2-required-validation-on-update
После того, как определены правила валидации для полей формы, можно столкнуться с проблемой. Лично у меня проблема была следующая. Загрузка картинки обязательна. При создании записи всё работает отлично, но вот при редактировании записи у меня опять же срабатывает валидация и поэтому модель само собой не сохраняется. Здесь необходимо воспользоваться валидацией по сценарию. В данной заметке я покажу простой пример валидации по сценарию путем переопределения метода yiibaseModel::scenarios()
.
Способ #1
Добавить в модель константу:
const SCENARIO_CREATE_POST = 'create_post';
Добавить в правила rules()
:
// Поле обязательно к заполнению если используется сценарий 'create_post' ['image', 'required', 'on' => self::SCENARIO_CREATE_POST]
В методе actionCreate()
контроллера PostController
:
$model = new Post(['scenario' => Post::SCENARIO_CREATE_POST]); // Или $model = new Post(); $model->scenario = $model::SCENARIO_CREATE_POST;
Теперь поле image
будет обязательным для заполнении только при создании записи.
Способ #2
Добавить в модель константу:
const SCENARIO_UPDATE_POST = 'update_post';
Добавить в правила rules()
:
// Поле обязательно, но валидацию исключить по данному сценарию ['image', 'required', 'except' => self::SCENARIO_UPDATE_POST]
В методе actionUpdate()
контроллера PostController
:
$model = $this->findModel($id); $model->scenario = $model::SCENARIO_UPDATE_POST;
Здесь поле image
будет так же обязательным для заполнении только при создании записи. (except
— за исключением обновления записи).
Способ #3
Данный способ основан на официальной документации
Добавить в модель константу:
const SCENARIO_UPDATE_POST = 'update_post';
Добавить в правила rules()
:
// Поле обязательно к заполнению ['image', 'required']
Добавить метод scenarios()
:
public function scenarios() { $scenarios = parent::scenarios(); // Валидация по указанному сценарию применяется только для поля "name" $scenarios[self::SCENARIO_UPDATE_POST] = ['name']; return $scenarios; }
Или так:
public function scenarios() { return ArrayHelper::merge(parent::scenarios(), [ self::SCENARIO_UPDATE_POST => ['name'] ]); }
Модели являются частью архитектуры MVC. Это объекты, представляющие бизнес-данные, правила и логику. Вы можете создавать классы моделей, расширяя yiibaseModel
или его дочерние классы. Базовый класс yiibaseModel
поддерживает множество полезных функций:
- Атрибуты: представляют бизнес-данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов: отображаемые метки для атрибутов;
- Массивное назначение: поддерживает заполнение нескольких атрибутов за один шаг;
- Правила валидации: обеспечивает ввод данных на основе объявленных правил проверки;
- Экспорт данных: позволяет экспортировать модельные данные в виде массивов с настраиваемыми форматами.
Класс Model также является базовым классом для более продвинутых моделей, таких как Active Record.
Атрибуты
Модели представляют бизнес-данные в терминах атрибутов. Каждый атрибут подобен общедоступному свойству модели. Метод yiibaseModel::attributes()
определяет атрибуты, которые имеет класс модели.
Также можно получить доступ к атрибуту, например, к свойству нормального объекта:
$model = new appmodelsContactForm;
// "name" атрибут ContactForm
$model->name = 'example';
echo $model->name;
Также можно получить доступ к таким атрибутам, как обращение к элементам массива, благодаря поддержке ArrayAccess и Traversable с помощью yiibaseModel
:
$model = new appmodelsContactForm;
// Доступ к атрибутам, таким как элементы массива
$model['name'] = 'example';
echo $model['name'];
// Модель перемещается с использованием foreach.
foreach ($model as $name => $value) {
echo "$name: $valuen";
}
Объявление атрибутов
По умолчанию, если ваш класс модели распространяется непосредственно от yiibaseModel
, все его нестатические переменные-члены являются атрибутами. Например, класс модели ContactForm, приведенный ниже, имеет четыре атрибута: name, address, email, subject, и body. Модель ContactForm используется для представления входных данных, полученных из HTML-формы.
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
Вы можете переопределить yiibaseModel::attributes()
для определения атрибутов по-другому. Метод должен возвращать имена атрибутов в модели. Например, yiidbActiveRecord
делает это, возвращая имена столбцов связанной таблицы базы данных в качестве имен атрибутов. Обратите внимание, что вам также может понадобиться переопределить магические методы, такие как __get ()
, __set ()
, чтобы доступ к атрибутам осуществлялся как обычные свойства объекта.
Метки атрибутов
При отображении значений или получении входных данных для атрибутов часто требуется отображать некоторые метки, связанные с атрибутами. Например, если задан атрибут с именем firstName, вы можете захотеть отобразить метку First Name, которая будет более удобной для пользователей, когда она будет отображаться конечным пользователям в таких местах, как входы формы и сообщения об ошибках.
Вы можете получить метку атрибута, вызывая yiibaseModel::getAttributeLabel()
. Например:
$model = new appmodelsContactForm;
// отображение "Name"
echo $model->getAttributeLabel('name');
По умолчанию метки атрибутов автоматически генерируются из имен атрибутов. Генерация выполняется методом yiibaseModel::generateAttributeLabel()
. Он переведет имена переменных в CamelCase стиль, при котором несколько слов будут написаны слитно без пробелов, при этом каждое слово пишется с заглавной буквы (в верхнем регистре). Например, username становится Username, а firstName становится FirstName.
Если вы не хотите использовать автоматически создаваемые ярлыки, вы можете переопределить yiibaseModel::attributeLabels()
, чтобы явно объявлять метки атрибутов. Например:
namespace appmodels;
use yiibaseModel;
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'),
];
}
Вы даже можете условно определить метки атрибутов. Например, на основе сценария, в котором используется модель, вы можете возвращать разные метки для одного и того же атрибута.
Сценарии (Scenarios)
Модель может использоваться в разных сценариях. Например, модель User может использоваться для сбора входных данных входа пользователя, но она может также использоваться для целей регистрации пользователя. В разных сценариях модель может использовать разные бизнес-правила и логику. Например, атрибут email может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему.
Модель использует свойство yiibaseModel::$scenario
для отслеживания сценария, в котором оно используется. По умолчанию модель поддерживает только один сценарий с именем default. В следующем коде показаны два способа установки сценария модели:
// Сценарий задается как свойство
$model = new User;
$model->scenario = User::SCENARIO_LOGIN;
// Сценарий настраивается с помощью конфигурации
$model = new User(['scenario' => User::SCENARIO_LOGIN]);
По умолчанию сценарии, поддерживаемые моделью, определяются правилами проверки, объявленными в модели. Однако вы можете настроить это поведение, переопределив метод yiibaseModel::scenarios()
, как показано ниже:
namespace appmodels;
use yiidbActiveRecord;
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'],
];
}
}
Метод scenarios()
возвращает массив, ключи которого являются именами сценариев и значениями соответствующих активных атрибутов. Активный атрибут может быть широко назначен и подлежит валидации. В приведенном выше примере атрибуты username и password активны в сценарии login. В то время как в сценарии register, кроме username и password, активен также email.
По умолчанию реализация scenarios() вернет все сценарии, найденные в методе объявления правила проверки правильности yiibaseModel::rules()
. При переопределении scenarios()
, если вы хотите ввести новые сценарии в дополнение к умолчанию, вы можете написать следующий код:
namespace appmodels;
use yiidbActiveRecord;
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 содержит действительный адрес электронной почты. Если значения для некоторых атрибутов не удовлетворяют соответствующим бизнес-правилам, необходимо отобразить соответствующие сообщения об ошибках, чтобы помочь пользователю исправить ошибки.
Вы можете вызвать yiibaseModel::validate()
для проверки полученных данных. Метод будет использовать правила проверки, объявленные в yiibaseModel::rules()
, для проверки всех соответствующих атрибутов. Если ошибка не найдена, она вернет true. В противном случае он сохранит ошибки в свойстве yiibaseModel::$errors
и вернет false. Например:
$model = new appmodelsContactForm;
// Заполнять атрибуты модели пользовательскими данными
$model->attributes = Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// все данные валидны
} else {
// Ошибка проверки: $ errors - массив, содержащий сообщения об ошибках
$errors = $model->errors;
}
Чтобы объявить правила проверки, связанные с моделью, переопределите метод yiibaseModel::rules()
, возвратив правила, которые должны удовлетворять атрибутам модели. В следующем примере показаны правила проверки, объявленные для модели ContactForm:
public function rules()
{
return [
// Необходимо указатьname, email, subject и body атрибуты
[['name', 'email', 'subject', 'body'], 'required'],
// Атрибут email должен быть правильным адресом электронной почты
['email', 'email'],
];
}
Правило может использоваться для проверки одного или нескольких атрибутов, а атрибут может быть проверен одним или несколькими правилами. Иногда вы можете захотеть применить правило только в определенных сценариях. Чтобы сделать это, вы можете указать свойство on для правила, например:
public function rules()
{
return [
// username, email и password требуются в сценарии "register"
[['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER],
// username и password требуются в сценарии "login"
[['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN],
];
}
Если вы не укажете свойство on, это правило будет применяться во всех сценариях. Правило называется активным правилом, если оно может быть применено в текущем сценарии.
Атрибут будет проверен, если и только если он является активным атрибутом, объявленным в scenarios()
, и связан с одним или несколькими активными правилами, объявленными в rules()
.
Массовое назначение
Массивное назначение — это удобный способ заполнения модели пользовательскими данными с использованием одной строки кода. Он заполняет атрибуты модели, назначая входные данные непосредственно атрибуту yiibaseModel::$attributes
. Следующие два фрагмента кода эквивалентны, поскольку оба пытаются присвоить данные формы, представленные конечными пользователями, атрибутам модели ContactForm. Очевидно, что первый, который использует массовое назначение, гораздо чище и менее подвержен ошибкам, чем последний:
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm;
$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;
Безопасные атрибуты
Массовое назначение применяется только к так называемым безопасным атрибутам, которые являются атрибутами, перечисленными в yiibaseModel::scenarios()
для текущего сценария модели. Например, если модель User имеет следующее объявление сценария, тогда, когда текущим сценарием является login, могут быть назначены только username и password. Любые другие атрибуты будут сохранены нетронутыми.
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
Поскольку по умолчанию реализация yiibaseModel::scenarios()
вернет все сценарии и атрибуты, найденные в yiibaseModel::rules()
, если вы не переопределите этот метод, это означает, что атрибут безопасен когда появляется в одном из активных правил проверки.
По этой причине специальный валидатор с защитой от алиасов предоставлен для того, чтобы вы могли объявить атрибут безопасным без его фактической проверки. Например, следующие правила объявляют, что и title и description являются безопасными атрибутами.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Небезопасные атрибуты
Как описано выше, метод yiibaseModel::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. Процесс экспорта можно разбить на два независимых шага:
- Модели преобразуются в массивы;
- Массивы преобразуются в целевые форматы.
Вы можете просто сосредоточиться на первом шаге, потому что второй шаг может быть достигнут универсальными форматерами данных, такими как yiiwebJsonResponseFormatter. Самый простой способ преобразования модели в массив — использовать свойство yiibaseModel::$attributes
. Например:
$post = appmodelsPost::findOne(100);
$array = $post->attributes;
По умолчанию свойство yiibaseModel::$attributes
возвращает значения всех атрибутов, объявленных в yiibaseModel::attributes()
.
Более гибким и мощным способом преобразования модели в массив является использование метода yiibaseModel::toArray()
. Его поведение по умолчанию такое же, как у атрибутов yiibaseModel::$
. Тем не менее, он позволяет вам выбирать, какие элементы данных, называемые полями, должны быть помещены в результирующий массив и как они должны быть отформатированы.
Поля
Поле — это просто именованный элемент в массиве, который получается вызовом метода yiibaseModel::toArray()
модели.
По умолчанию имена полей эквивалентны именам атрибутов. Однако вы можете изменить это поведение, переопределив методы fields()
или extraFields()
. Оба метода должны возвращать список определений полей. Поля, определенные fields()
, являются полями по умолчанию, что означает, что toArray()
возвратит эти поля по умолчанию. Метод extraFields()
определяет дополнительно доступные поля, которые также могут быть возвращены toArray()
, если вы укажете их через параметр $expand
. Например, следующий код будет возвращать все поля, определенные в fields()
и поля prettyName
и fullAddress
, если они определены в extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Вы можете переопределить fields()
для добавления, удаления, переименования или переопределения полей. Возвращаемое значение fields()
должно быть массивом. Ключами массива являются имена полей, а значениями массива являются соответствующие определения полей, которые могут быть либо именами свойств/атрибутов, либо анонимными функциями, возвращающими соответствующие значения полей. В особом случае, когда имя поля совпадает с именем определяющего атрибута, вы можете пропустить ключ массива. Например:
public function fields()
{
return [
// Имя поля совпадает с именем атрибута
'id',
// Имя поля - "email", соответствующее имя атрибута - "email_address"
'email' => 'email_address',
// Имя поля -"name", его значение определяется обратным вызовом PHP
'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;
}
Объясните, пожалуйста, доступно, что такое сценарии
-
Andr
- Сообщения: 69
- Зарегистрирован: 2015.04.05, 13:11
Объясните, пожалуйста, доступно, что такое сценарии
Здравствуйте. Всё, что написано про сценарии где-либо мне непонятно, поэтому, может я задам свои вопросы:
Сценарии позволяют не производить валидацию всех полей, а только тех, которые сейчас используются?
Как и где выбирается используемый в данный момент сценарий?
Вот, установил я в модели сценарии, а какой использовать нужно сказать контроллеру или экшену?
Как видите, у меня полное непонимание. Может, по вопросам будет понятно, как именно я не понимаю.
Попробуйте хоть как-то объяснить, а то уже не могу жить с таким непониманием. Спасибо большое!
-
SiZE
- Сообщения: 2771
- Зарегистрирован: 2011.09.21, 12:39
- Откуда: Perm
- Контактная информация:
Re: Объясните, пожалуйста, доступно, что такое сценарии
Сообщение
SiZE » 2016.02.28, 23:06
Сценарий это атрибут public $scenario в модели, которому ты присваиваешь некоторое значение. В будущем ты можешь опираться на это значение и производить какие-то действия внутри модели.
Из коробки сценарии используются:
1) Для заполнения атрибутов модели значениями, получаемыми от пользователя. У нас есть таблица user(id, email, password_hash, is_admin). Чтобы разрешить редактировать пользователю только email мы можем написать конструкцию:
Код: Выделить всё
if (isset($_POST['email'])) {
$model->email = $_POST['email'];
// дальнейшие действия
}
Если полей много, код быстро станет грязным. Поэтому мы просто указываем поле в методе rules нашей модели и соответствующий сценарий. И не беспокоимся, что пользователь передаст в запросе is_admin и сделает себя администратором.
2) Для валидации данных, которые: а) мы получим от пользователя б) должны соответствовать нашим задачам. Например при первичном сохранении модели user мы должны обязательно заполнить email, а при повторном редактировании, только если он изменился, т.е. поле становится необязательным.
Дополнительно можно использовать так:
Код: Выделить всё
public function beforeSave($insert)
{
if ($this->scenario == 'insert') {
$this->created_at = new Expression('NOW()');
} elseif ($this->scenario == 'update') {
$this->update_at = new Expression('NOW()');
}
}
-
chungachguk
- Сообщения: 435
- Зарегистрирован: 2012.07.17, 11:52
Re: Объясните, пожалуйста, доступно, что такое сценарии
Сообщение
chungachguk » 2016.02.29, 05:56
http://www.yiiframework.com/doc-2.0/gui … #scenarios
Из документации.
Создание модели
Код: Выделить всё
// 1. вариант Создание модели и установка свойства
$model = new User;
$model->scenario = 'login';
// 2. вариант Передача массива конфигурации при создании модели
$model = new User(['scenario' => 'login']);
В модели есть метод в котором описаны сценарии
Код: Выделить всё
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
Теперь, когда ты будешь выполнять присваивание значений полям посредством http://www.yiiframework.com/doc-2.0/gui … assignment
Код: Выделить всё
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post('ContactForm');
У тебя автоматически будут заполняться и валидироваться только поля ‘username’ и ‘password’. Остальные поля модели будут проигнорированы. И чтобы указать значение, например, полю email, придётся делать это вручную
Если коротко, то сценарий — это набор полей модели, с которыми мы хотим работать в данный момент.
Models are part of the MVC architecture.
They are objects representing business data, rules and logic.
You can create model classes by extending [[yiibaseModel]] or its child classes. The base class
[[yiibaseModel]] supports many useful features:
- Attributes: represent the business data and can be accessed like normal object properties
or array elements; - Attribute labels: specify the display labels for attributes;
- Massive assignment: supports populating multiple attributes in a single step;
- Validation rules: ensures input data based on the declared validation rules;
- Data Exporting: allows model data to be exported in terms of arrays with customizable formats.
The Model
class is also the base class for more advanced models, such as Active Record.
Please refer to the relevant documentation for more details about these advanced models.
Info: You are not required to base your model classes on [[yiibaseModel]]. However, because there are many Yii
components built to support [[yiibaseModel]], it is usually the preferable base class for a model.
Attributes
Models represent business data in terms of attributes. Each attribute is like a publicly accessible property
of a model. The method [[yiibaseModel::attributes()]] specifies what attributes a model class has.
You can access an attribute like accessing a normal object property:
$model = new appmodelsContactForm; // "name" is an attribute of ContactForm $model->name = 'example'; echo $model->name;
You can also access attributes like accessing array elements, thanks to the support for
ArrayAccess and ArrayIterator
by [[yiibaseModel]]:
$model = new appmodelsContactForm; // accessing attributes like array elements $model['name'] = 'example'; echo $model['name']; // iterate attributes foreach ($model as $name => $value) { echo "$name: $valuen"; }
Defining Attributes
By default, if your model class extends directly from [[yiibaseModel]], all its non-static public member
variables are attributes. For example, the ContactForm
model class below has four attributes: name
, email
,
subject
and body
. The ContactForm
model is used to represent the input data received from an HTML form.
namespace appmodels; use yiibaseModel; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
You may override [[yiibaseModel::attributes()]] to define attributes in a different way. The method should
return the names of the attributes in a model. For example, [[yiidbActiveRecord]] does so by returning
the column names of the associated database table as its attribute names. Note that you may also need to
override the magic methods such as __get()
, __set()
so that the attributes can be accessed like
normal object properties.
Attribute Labels
When displaying values or getting input for attributes, you often need to display some labels associated
with attributes. For example, given an attribute named firstName
, you may want to display a label First Name
which is more user-friendly when displayed to end users in places such as form inputs and error messages.
You can get the label of an attribute by calling [[yiibaseModel::getAttributeLabel()]]. For example,
$model = new appmodelsContactForm; // displays "Name" echo $model->getAttributeLabel('name');
By default, attribute labels are automatically generated from attribute names. The generation is done by
the method [[yiibaseModel::generateAttributeLabel()]]. It will turn camel-case variable names into
multiple words with the first letter in each word in upper case. For example, username
becomes Username
,
and firstName
becomes First Name
.
If you do not want to use automatically generated labels, you may override [[yiibaseModel::attributeLabels()]]
to explicitly declare attribute labels. For example,
namespace appmodels; use yiibaseModel; 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', ]; } }
For applications supporting multiple languages, you may want to translate attribute labels. This can be done
in the [[yiibaseModel::attributeLabels()|attributeLabels()]] method as well, like the following:
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'), ]; }
You may even conditionally define attribute labels. For example, based on the scenario the model
is being used in, you may return different labels for the same attribute.
Info: Strictly speaking, attribute labels are part of views. But declaring labels
in models is often very convenient and can result in very clean and reusable code.
Scenarios
A model may be used in different scenarios. For example, a User
model may be used to collect user login inputs,
but it may also be used for the user registration purpose. In different scenarios, a model may use different
business rules and logic. For example, the email
attribute may be required during user registration,
but not so during user login.
A model uses the [[yiibaseModel::scenario]] property to keep track of the scenario it is being used in.
By default, a model supports only a single scenario named default
. The following code shows two ways of
setting the scenario of a model:
// 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]);
By default, the scenarios supported by a model are determined by the validation rules declared
in the model. However, you can customize this behavior by overriding the [[yiibaseModel::scenarios()]] method,
like the following:
namespace appmodels; use yiidbActiveRecord; 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'], ]; } }
Info: In the above and following examples, the model classes are extending from [[yiidbActiveRecord]]
because the usage of multiple scenarios usually happens to Active Record classes.
The scenarios()
method returns an array whose keys are the scenario names and values the corresponding
active attributes. An active attribute can be massively assigned and is subject
to validation. In the above example, the username
and password
attributes are active
in the login
scenario; while in the register
scenario, email
is also active besides username
and password
.
The default implementation of scenarios()
will return all scenarios found in the validation rule declaration
method [[yiibaseModel::rules()]]. When overriding scenarios()
, if you want to introduce new scenarios
in addition to the default ones, you may write code like the following:
namespace appmodels; use yiidbActiveRecord; 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; } }
The scenario feature is primarily used by validation and massive attribute assignment.
You can, however, use it for other purposes. For example, you may declare attribute labels
differently based on the current scenario.
Validation Rules
When the data for a model is received from end users, it should be validated to make sure it satisfies
certain rules (called validation rules, also known as business rules). For example, given a ContactForm
model,
you may want to make sure all attributes are not empty and the email
attribute contains a valid email address.
If the values for some attributes do not satisfy the corresponding business rules, appropriate error messages
should be displayed to help the user to fix the errors.
You may call [[yiibaseModel::validate()]] to validate the received data. The method will use
the validation rules declared in [[yiibaseModel::rules()]] to validate every relevant attribute. If no error
is found, it will return true. Otherwise, it will keep the errors in the [[yiibaseModel::errors]] property
and return false. For example,
$model = new appmodelsContactForm; // 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; }
To declare validation rules associated with a model, override the [[yiibaseModel::rules()]] method by returning
the rules that the model attributes should satisfy. The following example shows the validation rules declared
for the ContactForm
model:
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'], ]; }
A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules.
Please refer to the Validating Input section for more details on how to declare
validation rules.
Sometimes, you may want a rule to be applied only in certain scenarios. To do so, you can
specify the on
property of a rule, like the following:
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], ]; }
If you do not specify the on
property, the rule would be applied in all scenarios. A rule is called
an active rule if it can be applied in the current [[yiibaseModel::scenario|scenario]].
An attribute will be validated if and only if it is an active attribute declared in scenarios()
and
is associated with one or multiple active rules declared in rules()
.
Massive Assignment
Massive assignment is a convenient way of populating a model with user inputs using a single line of code.
It populates the attributes of a model by assigning the input data directly to the [[yiibaseModel::$attributes]]
property. The following two pieces of code are equivalent, both trying to assign the form data submitted by end users
to the attributes of the ContactForm
model. Clearly, the former, which uses massive assignment, is much cleaner
and less error prone than the latter:
$model = new appmodelsContactForm; $model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm; $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;
Safe Attributes
Massive assignment only applies to the so-called safe attributes which are the attributes listed in
[[yiibaseModel::scenarios()]] for the current [[yiibaseModel::scenario|scenario]] of a model.
For example, if the User
model has the following scenario declaration, then when the current scenario
is login
, only the username
and password
can be massively assigned. Any other attributes will
be kept untouched.
public function scenarios() { return [ self::SCENARIO_LOGIN => ['username', 'password'], self::SCENARIO_REGISTER => ['username', 'email', 'password'], ]; }
Info: The reason that massive assignment only applies to safe attributes is because you want to
control which attributes can be modified by end user data. For example, if theUser
model
has apermission
attribute which determines the permission assigned to the user, you would
like this attribute to be modifiable by administrators through a backend interface only.
Because the default implementation of [[yiibaseModel::scenarios()]] will return all scenarios and attributes
found in [[yiibaseModel::rules()]], if you do not override this method, it means an attribute is safe as long
as it appears in one of the active validation rules.
For this reason, a special validator aliased safe
is provided so that you can declare an attribute
to be safe without actually validating it. For example, the following rules declare that both title
and description
are safe attributes.
public function rules() { return [ [['title', 'description'], 'safe'], ]; }
Unsafe Attributes
As described above, the [[yiibaseModel::scenarios()]] method serves for two purposes: determining which attributes
should be validated, and determining which attributes are safe. In some rare cases, you may want to validate
an attribute but do not want to mark it safe. You can do so by prefixing an exclamation mark !
to the attribute
name when declaring it in scenarios()
, like the secret
attribute in the following:
public function scenarios() { return [ self::SCENARIO_LOGIN => ['username', 'password', '!secret'], ]; }
When the model is in the login
scenario, all three attributes will be validated. However, only the username
and password
attributes can be massively assigned. To assign an input value to the secret
attribute, you
have to do it explicitly as follows,
$model->secret = $secret;
Data Exporting
Models often need to be exported in different formats. For example, you may want to convert a collection of
models into JSON or Excel format. The exporting process can be broken down into two independent steps.
In the first step, models are converted into arrays; in the second step, the arrays are converted into
target formats. You may just focus on the first step, because the second step can be achieved by generic
data formatters, such as [[yiiwebJsonResponseFormatter]].
The simplest way of converting a model into an array is to use the [[yiibaseModel::$attributes]] property.
For example,
$post = appmodelsPost::findOne(100); $array = $post->attributes;
By default, the [[yiibaseModel::$attributes]] property will return the values of all attributes
declared in [[yiibaseModel::attributes()]].
A more flexible and powerful way of converting a model into an array is to use the [[yiibaseModel::toArray()]]
method. Its default behavior is the same as that of [[yiibaseModel::$attributes]]. However, it allows you
to choose which data items, called fields, to be put in the resulting array and how they should be formatted.
In fact, it is the default way of exporting models in RESTful Web service development, as described in
the Response Formatting.
Fields
A field is simply a named element in the array that is obtained by calling the [[yiibaseModel::toArray()]] method
of a model.
By default, field names are equivalent to attribute names. However, you can change this behavior by overriding
the [[yiibaseModel::fields()|fields()]] and/or [[yiibaseModel::extraFields()|extraFields()]] methods. Both methods
should return a list of field definitions. The fields defined by fields()
are default fields, meaning that
toArray()
will return these fields by default. The extraFields()
method defines additionally available fields
which can also be returned by toArray()
as long as you specify them via the $expand
parameter. For example,
the following code will return all fields defined in fields()
and the prettyName
and fullAddress
fields
if they are defined in extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
You can override fields()
to add, remove, rename or redefine fields. The return value of fields()
should be an array. The array keys are the field names, and the array values are the corresponding
field definitions which can be either property/attribute names or anonymous functions returning the
corresponding field values. In the special case when a field name is the same as its defining attribute
name, you can omit the array key. For example,
// 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; }
Warning: Because by default all attributes of a model will be included in the exported array, you should
examine your data to make sure they do not contain sensitive information. If there is such information,
you should overridefields()
to filter them out. In the above example, we choose
to filter outauth_key
,password_hash
andpassword_reset_token
.
Best Practices
Models are the central places to represent business data, rules and logic. They often need to be reused
in different places. In a well-designed application, models are usually much fatter than
controllers.
In summary, models
- may contain attributes to represent business data;
- may contain validation rules to ensure the data validity and integrity;
- may contain methods implementing business logic;
- should NOT directly access request, session, or any other environmental data. These data should be injected
by controllers into models; - should avoid embedding HTML or other presentational code — this is better done in views;
- avoid having too many scenarios in a single model.
You may usually consider the last recommendation above when you are developing large complex systems.
In these systems, models could be very fat because they are used in many places and may thus contain many sets
of rules and business logic. This often ends up in a nightmare in maintaining the model code
because a single touch of the code could affect several different places. To make the model code more maintainable,
you may take the following strategy:
- Define a set of base model classes that are shared by different applications or
modules. These model classes should contain minimal sets of rules and logic that
are common among all their usages. - In each application or module that uses a model,
define a concrete model class by extending from the corresponding base model class. The concrete model classes
should contain rules and logic that are specific for that application or module.
For example, in the Advanced Project Template, you may define a base model
class commonmodelsPost
. Then for the front end application, you define and use a concrete model class
frontendmodelsPost
which extends from commonmodelsPost
. And similarly for the back end application,
you define backendmodelsPost
. With this strategy, you will be sure that the code in frontendmodelsPost
is only specific to the front end application, and if you make any change to it, you do not need to worry if
the change may break the back end application.
Models are part of the MVC architecture.
They are objects representing business data, rules and logic.
You can create model classes by extending [[yiibaseModel]] or its child classes. The base class
[[yiibaseModel]] supports many useful features:
- Attributes: represent the business data and can be accessed like normal object properties
or array elements; - Attribute labels: specify the display labels for attributes;
- Massive assignment: supports populating multiple attributes in a single step;
- Validation rules: ensures input data based on the declared validation rules;
- Data Exporting: allows model data to be exported in terms of arrays with customizable formats.
The Model
class is also the base class for more advanced models, such as Active Record.
Please refer to the relevant documentation for more details about these advanced models.
Info: You are not required to base your model classes on [[yiibaseModel]]. However, because there are many Yii
components built to support [[yiibaseModel]], it is usually the preferable base class for a model.
Attributes
Models represent business data in terms of attributes. Each attribute is like a publicly accessible property
of a model. The method [[yiibaseModel::attributes()]] specifies what attributes a model class has.
You can access an attribute like accessing a normal object property:
$model = new appmodelsContactForm; // "name" is an attribute of ContactForm $model->name = 'example'; echo $model->name;
You can also access attributes like accessing array elements, thanks to the support for
ArrayAccess and ArrayIterator
by [[yiibaseModel]]:
$model = new appmodelsContactForm; // accessing attributes like array elements $model['name'] = 'example'; echo $model['name']; // iterate attributes foreach ($model as $name => $value) { echo "$name: $valuen"; }
Defining Attributes
By default, if your model class extends directly from [[yiibaseModel]], all its non-static public member
variables are attributes. For example, the ContactForm
model class below has four attributes: name
, email
,
subject
and body
. The ContactForm
model is used to represent the input data received from an HTML form.
namespace appmodels; use yiibaseModel; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
You may override [[yiibaseModel::attributes()]] to define attributes in a different way. The method should
return the names of the attributes in a model. For example, [[yiidbActiveRecord]] does so by returning
the column names of the associated database table as its attribute names. Note that you may also need to
override the magic methods such as __get()
, __set()
so that the attributes can be accessed like
normal object properties.
Attribute Labels
When displaying values or getting input for attributes, you often need to display some labels associated
with attributes. For example, given an attribute named firstName
, you may want to display a label First Name
which is more user-friendly when displayed to end users in places such as form inputs and error messages.
You can get the label of an attribute by calling [[yiibaseModel::getAttributeLabel()]]. For example,
$model = new appmodelsContactForm; // displays "Name" echo $model->getAttributeLabel('name');
By default, attribute labels are automatically generated from attribute names. The generation is done by
the method [[yiibaseModel::generateAttributeLabel()]]. It will turn camel-case variable names into
multiple words with the first letter in each word in upper case. For example, username
becomes Username
,
and firstName
becomes First Name
.
If you do not want to use automatically generated labels, you may override [[yiibaseModel::attributeLabels()]]
to explicitly declare attribute labels. For example,
namespace appmodels; use yiibaseModel; 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', ]; } }
For applications supporting multiple languages, you may want to translate attribute labels. This can be done
in the [[yiibaseModel::attributeLabels()|attributeLabels()]] method as well, like the following:
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'), ]; }
You may even conditionally define attribute labels. For example, based on the scenario the model
is being used in, you may return different labels for the same attribute.
Info: Strictly speaking, attribute labels are part of views. But declaring labels
in models is often very convenient and can result in very clean and reusable code.
Scenarios
A model may be used in different scenarios. For example, a User
model may be used to collect user login inputs,
but it may also be used for the user registration purpose. In different scenarios, a model may use different
business rules and logic. For example, the email
attribute may be required during user registration,
but not so during user login.
A model uses the [[yiibaseModel::scenario]] property to keep track of the scenario it is being used in.
By default, a model supports only a single scenario named default
. The following code shows two ways of
setting the scenario of a model:
// 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]);
By default, the scenarios supported by a model are determined by the validation rules declared
in the model. However, you can customize this behavior by overriding the [[yiibaseModel::scenarios()]] method,
like the following:
namespace appmodels; use yiidbActiveRecord; 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'], ]; } }
Info: In the above and following examples, the model classes are extending from [[yiidbActiveRecord]]
because the usage of multiple scenarios usually happens to Active Record classes.
The scenarios()
method returns an array whose keys are the scenario names and values the corresponding
active attributes. An active attribute can be massively assigned and is subject
to validation. In the above example, the username
and password
attributes are active
in the login
scenario; while in the register
scenario, email
is also active besides username
and password
.
The default implementation of scenarios()
will return all scenarios found in the validation rule declaration
method [[yiibaseModel::rules()]]. When overriding scenarios()
, if you want to introduce new scenarios
in addition to the default ones, you may write code like the following:
namespace appmodels; use yiidbActiveRecord; 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; } }
The scenario feature is primarily used by validation and massive attribute assignment.
You can, however, use it for other purposes. For example, you may declare attribute labels
differently based on the current scenario.
Validation Rules
When the data for a model is received from end users, it should be validated to make sure it satisfies
certain rules (called validation rules, also known as business rules). For example, given a ContactForm
model,
you may want to make sure all attributes are not empty and the email
attribute contains a valid email address.
If the values for some attributes do not satisfy the corresponding business rules, appropriate error messages
should be displayed to help the user to fix the errors.
You may call [[yiibaseModel::validate()]] to validate the received data. The method will use
the validation rules declared in [[yiibaseModel::rules()]] to validate every relevant attribute. If no error
is found, it will return true. Otherwise, it will keep the errors in the [[yiibaseModel::errors]] property
and return false. For example,
$model = new appmodelsContactForm; // 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; }
To declare validation rules associated with a model, override the [[yiibaseModel::rules()]] method by returning
the rules that the model attributes should satisfy. The following example shows the validation rules declared
for the ContactForm
model:
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'], ]; }
A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules.
Please refer to the Validating Input section for more details on how to declare
validation rules.
Sometimes, you may want a rule to be applied only in certain scenarios. To do so, you can
specify the on
property of a rule, like the following:
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], ]; }
If you do not specify the on
property, the rule would be applied in all scenarios. A rule is called
an active rule if it can be applied in the current [[yiibaseModel::scenario|scenario]].
An attribute will be validated if and only if it is an active attribute declared in scenarios()
and
is associated with one or multiple active rules declared in rules()
.
Massive Assignment
Massive assignment is a convenient way of populating a model with user inputs using a single line of code.
It populates the attributes of a model by assigning the input data directly to the [[yiibaseModel::$attributes]]
property. The following two pieces of code are equivalent, both trying to assign the form data submitted by end users
to the attributes of the ContactForm
model. Clearly, the former, which uses massive assignment, is much cleaner
and less error prone than the latter:
$model = new appmodelsContactForm; $model->attributes = Yii::$app->request->post('ContactForm');
$model = new appmodelsContactForm; $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;
Safe Attributes
Massive assignment only applies to the so-called safe attributes which are the attributes listed in
[[yiibaseModel::scenarios()]] for the current [[yiibaseModel::scenario|scenario]] of a model.
For example, if the User
model has the following scenario declaration, then when the current scenario
is login
, only the username
and password
can be massively assigned. Any other attributes will
be kept untouched.
public function scenarios() { return [ self::SCENARIO_LOGIN => ['username', 'password'], self::SCENARIO_REGISTER => ['username', 'email', 'password'], ]; }
Info: The reason that massive assignment only applies to safe attributes is because you want to
control which attributes can be modified by end user data. For example, if theUser
model
has apermission
attribute which determines the permission assigned to the user, you would
like this attribute to be modifiable by administrators through a backend interface only.
Because the default implementation of [[yiibaseModel::scenarios()]] will return all scenarios and attributes
found in [[yiibaseModel::rules()]], if you do not override this method, it means an attribute is safe as long
as it appears in one of the active validation rules.
For this reason, a special validator aliased safe
is provided so that you can declare an attribute
to be safe without actually validating it. For example, the following rules declare that both title
and description
are safe attributes.
public function rules() { return [ [['title', 'description'], 'safe'], ]; }
Unsafe Attributes
As described above, the [[yiibaseModel::scenarios()]] method serves for two purposes: determining which attributes
should be validated, and determining which attributes are safe. In some rare cases, you may want to validate
an attribute but do not want to mark it safe. You can do so by prefixing an exclamation mark !
to the attribute
name when declaring it in scenarios()
, like the secret
attribute in the following:
public function scenarios() { return [ self::SCENARIO_LOGIN => ['username', 'password', '!secret'], ]; }
When the model is in the login
scenario, all three attributes will be validated. However, only the username
and password
attributes can be massively assigned. To assign an input value to the secret
attribute, you
have to do it explicitly as follows,
$model->secret = $secret;
Data Exporting
Models often need to be exported in different formats. For example, you may want to convert a collection of
models into JSON or Excel format. The exporting process can be broken down into two independent steps.
In the first step, models are converted into arrays; in the second step, the arrays are converted into
target formats. You may just focus on the first step, because the second step can be achieved by generic
data formatters, such as [[yiiwebJsonResponseFormatter]].
The simplest way of converting a model into an array is to use the [[yiibaseModel::$attributes]] property.
For example,
$post = appmodelsPost::findOne(100); $array = $post->attributes;
By default, the [[yiibaseModel::$attributes]] property will return the values of all attributes
declared in [[yiibaseModel::attributes()]].
A more flexible and powerful way of converting a model into an array is to use the [[yiibaseModel::toArray()]]
method. Its default behavior is the same as that of [[yiibaseModel::$attributes]]. However, it allows you
to choose which data items, called fields, to be put in the resulting array and how they should be formatted.
In fact, it is the default way of exporting models in RESTful Web service development, as described in
the Response Formatting.
Fields
A field is simply a named element in the array that is obtained by calling the [[yiibaseModel::toArray()]] method
of a model.
By default, field names are equivalent to attribute names. However, you can change this behavior by overriding
the [[yiibaseModel::fields()|fields()]] and/or [[yiibaseModel::extraFields()|extraFields()]] methods. Both methods
should return a list of field definitions. The fields defined by fields()
are default fields, meaning that
toArray()
will return these fields by default. The extraFields()
method defines additionally available fields
which can also be returned by toArray()
as long as you specify them via the $expand
parameter. For example,
the following code will return all fields defined in fields()
and the prettyName
and fullAddress
fields
if they are defined in extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
You can override fields()
to add, remove, rename or redefine fields. The return value of fields()
should be an array. The array keys are the field names, and the array values are the corresponding
field definitions which can be either property/attribute names or anonymous functions returning the
corresponding field values. In the special case when a field name is the same as its defining attribute
name, you can omit the array key. For example,
// 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; }
Warning: Because by default all attributes of a model will be included in the exported array, you should
examine your data to make sure they do not contain sensitive information. If there is such information,
you should overridefields()
to filter them out. In the above example, we choose
to filter outauth_key
,password_hash
andpassword_reset_token
.
Best Practices
Models are the central places to represent business data, rules and logic. They often need to be reused
in different places. In a well-designed application, models are usually much fatter than
controllers.
In summary, models
- may contain attributes to represent business data;
- may contain validation rules to ensure the data validity and integrity;
- may contain methods implementing business logic;
- should NOT directly access request, session, or any other environmental data. These data should be injected
by controllers into models; - should avoid embedding HTML or other presentational code — this is better done in views;
- avoid having too many scenarios in a single model.
You may usually consider the last recommendation above when you are developing large complex systems.
In these systems, models could be very fat because they are used in many places and may thus contain many sets
of rules and business logic. This often ends up in a nightmare in maintaining the model code
because a single touch of the code could affect several different places. To make the model code more maintainable,
you may take the following strategy:
- Define a set of base model classes that are shared by different applications or
modules. These model classes should contain minimal sets of rules and logic that
are common among all their usages. - In each application or module that uses a model,
define a concrete model class by extending from the corresponding base model class. The concrete model classes
should contain rules and logic that are specific for that application or module.
For example, in the Advanced Project Template, you may define a base model
class commonmodelsPost
. Then for the front end application, you define and use a concrete model class
frontendmodelsPost
which extends from commonmodelsPost
. And similarly for the back end application,
you define backendmodelsPost
. With this strategy, you will be sure that the code in frontendmodelsPost
is only specific to the front end application, and if you make any change to it, you do not need to worry if
the change may break the back end application.
- Attributes
- Scenarios
- Validation Rules
- Massive Assignment
- Data Exporting
- Best Practices
Модели являются частью архитектуры MVC . Это объекты, представляющие бизнес-данные, правила и логику.
Вы можете создавать классы моделей, расширяя yii base Model или его дочерние классы. Базовый класс yii base Model поддерживает множество полезных функций:
- Атрибуты : представляют бизнес-данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов : укажите отображаемые метки для атрибутов;
- Массовое назначение : поддерживает заполнение нескольких атрибутов за один шаг;
- Правила проверки : обеспечивает ввод данных на основе заявленных правил проверки;
- Экспорт данных : позволяет экспортировать данные модели в виде массивов с настраиваемыми форматами.
Класс Model
также является базовым классом для более сложных моделей, таких как Active Record . Пожалуйста, обратитесь к соответствующей документации для получения более подробной информации об этих расширенных моделях.
Информация: вы не обязаны основывать классы вашей модели на yii base Model . Однако, поскольку существует множество компонентов Yii, предназначенных для поддержки yii base Model , обычно это предпочтительный базовый класс для модели.
Attributes
Модели представляют бизнес-данные в терминах атрибутов . Каждый атрибут подобен общедоступному свойству модели. Метод yiibaseModel::attributes() указывает, какие атрибуты есть у класса модели.
Вы можете получить доступ к такому атрибуту,как доступ к обычному свойству объекта:
$model = new appmodelsContactForm; $model->name = 'example'; echo $model->name;
Вы также можете получить доступ к атрибутам, таким как доступ к элементам массива, благодаря поддержке ArrayAccess и Traversable в yii base Model :
$model = new appmodelsContactForm; $model['name'] = 'example'; echo $model['name']; foreach ($model as $name => $value) { echo "$name: $valuen"; }
Defining Attributes
По умолчанию, если класс вашей модели расширяется непосредственно из yii base Model , все его нестатические общедоступные переменные-члены являются атрибутами. Например, ContactForm
класс ниже модель имеет четыре атрибута: name
, по email
, subject
и body
. Модель ContactForm
используется для представления входных данных, полученных из HTML-формы.
namespace appmodels; use yiibaseModel; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
Вы можете переопределить yiibaseModel::attributes() , чтобы определить атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например, yiidbActiveRecord делает это, возвращая имена столбцов связанной таблицы базы данных в качестве имен атрибутов. Обратите внимание, что вам также может понадобиться переопределить магические методы, такие как __get()
, __set()
, чтобы к атрибутам можно было получить доступ как к обычным свойствам объекта.
Attribute Labels
При отображении значений или получении входных данных для атрибутов вам часто требуется отображать некоторые метки, связанные с атрибутами. Например, учитывая атрибут с именем firstName
, вы можете захотеть отобразить метку First Name
, которая более удобна для пользователя при отображении для конечных пользователей в таких местах, как ввод формы и сообщения об ошибках.
Вы можете получить метку атрибута, вызвав yiibaseModel::getAttributeLabel() . Например,
$model = new appmodelsContactForm; echo $model->getAttributeLabel('name');
По умолчанию метки атрибутов автоматически генерируются из имен атрибутов. Генерация выполняется методом yiibaseModel::generateAttributeLabel() . Это превратит имена переменных в верблюжьем регистре в несколько слов с первой буквой в каждом слове в верхнем регистре. Например, username
становится Username
, а firstName
становится First Name
.
Если вы не хотите использовать автоматически сгенерированные метки, вы можете переопределить yiibaseModel::attributeLabels() для явного объявления меток атрибутов. Например,
namespace appmodels; use yiibaseModel; 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'), ]; }
Вы даже можете условно определить метки атрибутов. Например, в зависимости от сценария, в котором используется модель, вы можете возвращать разные метки для одного и того же атрибута.
Информация: Строго говоря, метки атрибутов являются частью представлений . Но объявление меток в моделях часто очень удобно и может привести к очень чистому и многоразовому коду.
Scenarios
Модель может использоваться в разных сценариях . Например, модель User
может использоваться для сбора входных данных пользователя, но она также может использоваться для цели регистрации пользователя. В разных сценариях модель может использовать разные бизнес-правила и логику. Например, атрибут email
может потребоваться во время регистрации пользователя, но не во время входа в систему.
Модель использует свойство yii base Model :: $ сценария для отслеживания сценария, в котором она используется. По умолчанию модель поддерживает только один сценарий с именем default
. В следующем коде показаны два способа настройки сценария модели:
$model = new User; $model->scenario = User::SCENARIO_LOGIN; $model = new User(['scenario' => User::SCENARIO_LOGIN]);
По умолчанию сценарии, поддерживаемые моделью, определяются правилами проверки, объявленными в модели. Однако вы можете настроить это поведение, переопределив метод yii base Model :: scenarios () , как показано ниже:
namespace appmodels; use yiidbActiveRecord; 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
, помимо username
и password
также активна email
.
Реализация scenarios()
по умолчанию вернет все сценарии, найденные в методе объявления правила проверки yiibaseModel::rules() . При переопределении scenarios()
, если вы хотите ввести новые сценарии в дополнение к сценариям по умолчанию, вы можете написать следующий код:
namespace appmodels; use yiidbActiveRecord; 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; } }
Функция сценария в основном используется для проверки и массового присвоения атрибутов . Однако вы можете использовать его для других целей. Например, вы можете объявить метки атрибутов по- разному в зависимости от текущего сценария.
Validation Rules
Когда данные для модели получены от конечных пользователей, они должны быть проверены, чтобы убедиться, что они удовлетворяют определенным правилам (так называемым правилам проверки , также известным как бизнес-правила ). Например, учитывая модель ContactForm
, вы можете убедиться, что все атрибуты не пусты, а атрибут email
содержит действительный адрес электронной почты. Если значения некоторых атрибутов не удовлетворяют соответствующим бизнес-правилам, должны отображаться соответствующие сообщения об ошибках, чтобы помочь пользователю исправить ошибки.
Вы можете вызвать yiibaseModel::validate() для проверки полученных данных. Метод будет использовать правила проверки, объявленные в yiibaseModel::rules(), для проверки каждого соответствующего атрибута. Если ошибок не обнаружено, возвращается true
. В противном случае он сохранит ошибки в свойстве yiibaseModel::$errors и вернет false
. Например,
$model = new appmodelsContactForm; $model->attributes = Yii::$app->request->post('ContactForm'); if ($model->validate()) { } else { $errors = $model->errors; }
Чтобы объявить правила проверки, связанные с моделью, переопределите метод yiibaseModel::rules(), возвращая правила, которым должны удовлетворять атрибуты модели. В следующем примере показаны правила проверки, объявленные для модели ContactForm
:
public function rules() { return [ [['name', 'email', 'subject', 'body'], 'required'], ['email', 'email'], ]; }
Правило может использоваться для проверки одного или нескольких атрибутов, а атрибут может быть подтвержден одним или несколькими правилами. Пожалуйста, обратитесь к разделу « Проверка ввода » для получения дополнительной информации о том, как объявить правила проверки.
Иногда может потребоваться, чтобы правило применялось только в определенных сценариях . Для этого вы можете указать свойство on
правила, например:
public function rules() { return [ [['username', 'email', 'password'], 'required', 'on' => self::SCENARIO_REGISTER], [['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN], ]; }
Если вы не укажете свойство on
, правило будет применяться во всех сценариях. Правило называется активным правилом, если его можно применить в текущем сценарии .
Атрибут будет проверен тогда и только тогда, когда это активный атрибут, объявленный в scenarios()
и связанный с одним или несколькими активными правилами, объявленными в rules()
.
© 2008–2017 by Yii Software LLC
Licensed under the three clause BSD license.
http://www.yiiframework.com/doc-2.0/guide-structure-models.html
Yii
2.0
- Recommended Practices
- Filters
- Massive Assignment
- Modules