Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

409 lines
35 KiB

Модели
======
Модели являются частью архитектуры [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (Модель-Вид-Контроллер). Они представляют собой объекты бизнес данных, правил и логики.
Вы можете создавать классы моделей путём расширения класса [[yii\base\Model]] или его дочерних классов. Базовый класс [[yii\base\Model]] поддерживает много полезных функций:
* [Атрибуты](#attributes): представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массива;
* [Метки атрибутов](#attribute-labels): задают отображение атрибута;
* [Массовое присвоение](#massive-assignment): поддержка заполнения нескольких атрибутов в один шаг;
* [Правила проверки](#validation-rules): обеспечивают ввод данных на основе заявленных правил проверки;
* [Экспорт Данных](#data-exporting): разрешает данным модели быть экспортированными в массивы с настройкой форматов.
Класс `Model` также является базовым классом для многих расширенных моделей, таких как [Active Record](db-active-record.md). Пожалуйста, обратитесь к соответствующей документации для более подробной информации об этих расширенных моделях.
> Для справки: Вы не обязаны основывать свои классы моделей на [[yii\base\Model]]. Однако, поскольку в yii есть много компонентов, созданных для поддержки [[yii\base\Model]], обычно так делать предпочтительнее для базового класса модели.
## Атрибуты <span id="attributes"></span>
Модели предоставляют рабочие данные в терминах *атрибутах*. Каждый атрибут представляет собой публично доступное свойство модели. Метод [[yii\base\Model::attributes()]] определяет какие атрибуты имеет класс модели.
Вы можете получить доступ к атрибуту как к обычному свойству объекта:
```php
$model = new \app\models\ContactForm;
// "name" - это атрибут модели ContactForm
$model->name = 'example';
echo $model->name;
```
Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) и [ArrayIterator](http://php.net/manual/en/class.arrayiterator.php)
в [[yii\base\Model]]:
```php
$model = new \app\models\ContactForm;
// доступ к атрибутам как к элементам массива
$model['name'] = 'example';
echo $model['name'];
// перебор атрибутов
foreach ($model as $name => $value) {
echo "$name: $value\n";
}
```
### Определение Атрибутов <span id="defining-attributes"></span>
По умолчанию, если ваш класс модели расширяется напрямую от [[yii\base\Model]], то все *не статичные публичные* переменные являются атрибутами. Например, у класса модели `ContactForm` , который находится ниже, четыре атрибута: `name`, `email`, `subject` и `body`. Модель `ContactForm` используется для представления входных данных, полученных из HTML формы.
```php
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
```
Вы можете переопределить метод [[yii\base\Model::attributes()]], чтобы определять атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например [[yii\db\ActiveRecord]] делает так, возвращая имена столбцов из связанной таблицы базы данных в качестве имён атрибутов. Также может понадобиться переопределить магические методы, такие как `__get()`, `__set()` для того, что бы атрибуты могли быть доступны как обычные свойства объекта.
### Метки атрибутов <span id="attribute-labels"></span>
При отображении значений или при получении ввода значений атрибутов, часто требуется отобразить некоторые надписи, связанные с атрибутами. Например, если атрибут назван `firstName`, Вы можете отобразить его как `First Name`, что является более удобным для пользователя, в тех случаях, когда атрибут отображается конечным пользователям в таких местах, как форма входа и сообщения об ошибках.
Вы можете получить метку атрибута, вызвав [[yii\base\Model::getAttributeLabel()]]. Например,
```php
$model = new \app\models\ContactForm;
// отобразит "Name"
echo $model->getAttributeLabel('name');
```
По умолчанию, метки атрибутов автоматически генерируются из названия атрибута. Генерация выполняется методом [[yii\base\Model::generateAttributeLabel()]]. Он превращает первую букву каждого слова в верхний регистр, если имена переменных состоят из нескольких слов. Например, `username` станет `Username`, а `firstName` станет `First Name`.
Если Вы не хотите использовать автоматически сгенерированные метки, Вы можете переопределить метод [[yii\base\Model::attributeLabels()]], чтобы явно объявить метку атрибута. Например,
```php
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
public function attributeLabels()
{
return [
'name' => 'Your name',
'email' => 'Your email address',
'subject' => 'Subject',
'body' => 'Content',
];
}
}
```
Для приложений поддерживающих мультиязычность, Вы можете перевести метки атрибутов. Это можно сделать в методе [[yii\base\Model::attributeLabels()|attributeLabels()]] как показано ниже:
```php
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) и использованной в нём модели , Вы можете возвращать различные метки для одного и того же атрибута.
> Для справки: Строго говоря, метки атрибутов являются частью [видов](structure-views.md). Но объявление меток в моделях часто очень удобно и приводит к чистоте кода и повторному его использованию.
## Сценарии <span id="scenarios"></span>
Модель может быть использована в различных *сценариях*. Например, модель `User` может быть использована для коллекции входных логинов пользователей, а также может быть использована для цели регистрации пользователей.
В различных сценариях, модель может использовать различные бизнес-правила и логику. Например, атрибут `email` может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему.
Модель использует свойство [[yii\base\Model::scenario]], чтобы отслеживать сценарий, в котором она используется. По умолчанию, модель поддерживает только один сценарий с именем `default`. В следующем коде показано два способа установки сценария модели:
```php
// сценарий задается как свойство
$model = new User;
$model->scenario = User::SCENARIO_LOGIN;
// сценарий задается через конфигурацию
$model = new User(['scenario' => User::SCENARIO_LOGIN]);
```
По умолчанию сценарии, поддерживаемые моделью, определяются [правилами валидации](#validation-rules) объявленными
в модели. Однако, Вы можете изменить это поведение путем переопределения метода [[yii\base\Model::scenarios()]] как показано ниже:
```php
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
const SCENARIO_LOGIN = 'login';
const SCENARIO_REGISTER = 'register';
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
}
```
> Для справки: В приведенном выше и следующих примерах, классы моделей расширяются от [[yii\db\ActiveRecord]] потому, что использование нескольких сценариев обычно происходит от классов [Active Record](db-active-record.md).
Метод `scenarios()` возвращает массив, ключами которого являются имена сценариев, а значения - соответствующие *активные атрибуты*. Активные атрибуты могут быть [массово присвоены](#massive-assignment) и подлежат [валидации](#validation-rules). В приведенном выше примере, атрибуты `username` и `password` это активные атрибуты сценария `login`, а в сценарии `register` так же активным атрибутом является `email` вместе с `username` и `password`.
По умолчанию реализация `scenarios()` вернёт все найденные сценарии в правилах валидации задекларированных в методе [[yii\base\Model::rules()]]. При переопределении метода `scenarios()`, если Вы хотите ввести новые сценарии помимо стандартных, Вы можете написать код на основе следующего примера:
```php
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
const SCENARIO_LOGIN = 'login';
const SCENARIO_REGISTER = 'register';
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_LOGIN] = ['username', 'password'];
$scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password'];
return $scenarios;
}
}
```
Возможности сценариев в основном используются [валидацией](#validation-rules) и [массовым присвоением атрибутов](#massive-assignment). Однако, Вы можете использовать их и для других целей. Например, Вы можете различным образом объявлять [метки атрибутов](#attribute-labels) на основе текущего сценария.
## Правила валидации <span id="validation-rules"></span>
Когда данные модели, получены от конечных пользователей, они должны быть проверены, для того чтобы убедиться, что данные удовлетворяют определенным правилам (так называемым *правилам валидации* также известными как *бизнес-правила*). Например, дана модель `ContactForm`, возможно Вы захотите убедиться, что все атрибуты являются не пустыми значениями, а атрибут `email` содержит допустимый адрес электронной почты. Если значения нескольких атрибутов не удовлетворяют соответствующим бизнес-правилам, то должны быть показаны соответствующие сообщения об ошибках, чтобы помочь конечному пользователю исправить допущенные ошибки.
Вы можете вызвать [[yii\base\Model::validate()]] для проверки полученных данных. Данный метод будет использовать
правила валидации определённые в [[yii\base\Model::rules()]] для проверки каждого соответствующего атрибута. Если ошибок не найдено, то возвращается True, в противном случае возвращается false, а ошибки содержит свойство [[yii\base\Model::errors]]. Например,
```php
$model = new \app\models\ContactForm;
// модель заполнения атрибутов данными, вводимыми пользователем
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// все данные верны
} else {
// проверка не удалась: $errors - это массив содержащий сообщения об ошибках
$errors = $model->errors;
}
```
Объявляем правила валидации связанные с моделью, переопределяем метод [[yii\base\Model::rules()]] возврата правил атрибутов модели которые следует удовлетворить. В следующем примере показаны правила проверки объявленные в модели `ContactForm`:
```php
public function rules()
{
return [
// name, email, subject и body атрибуты обязательны
[['name', 'email', 'subject', 'body'], 'required'],
// атрибут email должен быть правильным email адресом
['email', 'email'],
];
}
```
Правило может использоваться для проверки одного или нескольких атрибутов, также и атрибут может быть проверен одним или несколькими правилами. Пожалуйста, обратитесь к разделу [Проверка входных значений](input-validation.md) для более подробной информации о том, как объявлять правила проверки.
Иногда необходимо, чтобы правила применялись только в определенных [сценариях](#scenarios). Чтобы это сделать необходимо указать свойство `on` в правилах, следующим образом:
```php
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`, то правило применяется во всех сценариях. Правило называется *активным правилом* если оно может быть применено в текущем сценарии [[yii\base\Model::scenario|scenario]].
Атрибут будет проверяться тогда и только тогда если он является активным атрибутом объявленным в `scenarios()` и
связанным с одним или несколькими активными правилами, объявленными в `rules()`.
## Массовое Присвоение <span id="massive-assignment"></span>
Массовое присвоение - это удобный способ заполнения модели данными вводимыми пользователем с помощью одной строки кода. Он заполняет атрибуты модели путем присвоения входных данных непосредственно свойству [[yii\base\Model::$attributes]]. Следующие два куска кода эквивалентны, они оба пытаются присвоить данные из формы представленные конечными пользователями атрибутам модели `ContactForm`. Ясно, что первый код гораздо чище и менее подвержен ошибкам, чем второй:
```php
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
```
```php
$model = new \app\models\ContactForm;
$data = \Yii::$app->request->post('ContactForm', []);
$model->name = isset($data['name']) ? $data['name'] : null;
$model->email = isset($data['email']) ? $data['email'] : null;
$model->subject = isset($data['subject']) ? $data['subject'] : null;
$model->body = isset($data['body']) ? $data['body'] : null;
```
### Безопасные Атрибуты <span id="safe-attributes"></span>
Массовое присвоение применяется только к так называемым *безопасным атрибутам*, которые являются атрибутами, перечисленными в [[yii\base\Model::scenarios()]] в текущем сценарии [[yii\base\Model::scenario|scenario]] модели. Например, если модель `User` имеет следующий заданный сценарий, в данном случае это сценарий `login`, то только `username` и `password` могут быть массово присвоены. Любые другие атрибуты останутся нетронутыми.
```php
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password'],
self::SCENARIO_REGISTER => ['username', 'email', 'password'],
];
}
```
> Для справки: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель `User` имеет атрибут `permission`, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс.
По умолчанию реализация [[yii\base\Model::scenarios()]] будет возвращать все сценарии и атрибуты найденные в [[yii\base\Model::rules()]], если не переопределить этот метод, это будет означать, что атрибут является безопасным только в случае, если он участвует в любом из активных правил проверки.
По этой причине существует специальный валидатор с псевдонимом `safe`, он предоставляет возможность объявить атрибут безопасным без фактической его проверки. Например, следующие правила определяют, что оба атрибута `title` и `description` являются безопасными атрибутами.
```php
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
```
### Небезопасные атрибуты <span id="unsafe-attributes"></span>
Как сказано выше, метод [[yii\base\Model::scenarios()]] служит двум целям: определения, какие атрибуты должны быть проверены, и определения, какие атрибуты являются безопасными (т.е. не требуют проверки). В некоторых случаях необходимо проверить атрибут не объявляя его безопасным. Вы можете сделать это с помощью префикса восклицательный знак `!` в имени атрибута при объявлении его в `scenarios()` как атрибут `secret` в следующем примере:
```php
public function scenarios()
{
return [
self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
];
}
```
Когда модель будет присутствовать в сценарии `login`, то все три эти атрибута будут проверены. Однако, только атрибуты `username` и `password` могут быть массово присвоены. Назначить входное значение атрибуту `secret` нужно явно следующим образом,
```php
$model->secret = $secret;
```
## Экспорт Данных <span id="data-exporting"></span>
Часто нужно экспортировать модели в различные форматы. Например, может потребоваться преобразовать коллекцию моделей в JSON или Excel формат. Процесс экспорта может быть разбит на два самостоятельных шага. На первом этапе модели преобразуются в массивы; на втором этапе массивы преобразуются в целевые форматы. Вы можете сосредоточиться только на первом шаге потому, что второй шаг может быть достигнут путем универсального инструмента форматирования данных, такого как [[yii\web\JsonResponseFormatter]].
Самый простой способ преобразования модели в массив - использовать свойство [[yii\base\Model::$attributes]].
Например,
```php
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
```
По умолчанию, свойство [[yii\base\Model::$attributes]] возвращает значения *всех* атрибутов объявленных в [[yii\base\Model::attributes()]].
Более гибкий и мощный способ конвертирования модели в массив - использовать метод [[yii\base\Model::toArray()]]. Его поведение по умолчанию такое же как и у [[yii\base\Model::$attributes]]. Тем не менее, он позволяет выбрать, какие элементы данных, называемые *полями*, поставить в результирующий массив и как они должны быть отформатированы. На самом деле, этот способ экспорта моделей по умолчанию применяется при разработке в RESTful Web service, как описано в [Response Formatting](rest-response-formatting.md).
### Поля <span id="fields"></span>
Поле - это просто именованный элемент в массиве, который может быть получен вызовом метода [[yii\base\Model::toArray()]] модели.
По умолчанию имена полей эквивалентны именам атрибутов. Однако, это поведение можно изменить, переопределив методы
[[yii\base\Model::fields()|fields()]] и/или [[yii\base\Model::extraFields()|extraFields()]]. Оба метода должны возвращать список определенных полей. Поля определённые `fields()` являются полями по умолчанию, это означает, что `toArray()` будет возвращать эти поля по умолчанию. Метод `extraFields()` определяет дополнительно доступные поля, которые также могут быть возвращены `toArray()` так много, как Вы укажите их через параметр `$expand`. Например, следующий код будет возвращать все поля определённые в `fields()`, а также поля `prettyName` и `fullAddress`, если они определены в `extraFields()`.
```php
$array = $model->toArray([], ['prettyName', 'fullAddress']);
```
Вы можете переопределить `fields()` чтобы добавить, удалить, переименовать или переопределить поля. Возвращаемым значением `fields()` должен быть массив. Ключами массива являются имена полей, а значениями - соответствующие определения полей, которые могут быть либо именами свойств/атрибутов, либо анонимными функциями, возвращающими соответствующие значения полей. В частном случае, когда имя поля совпадает с именем его атрибута, возможно опустить ключ массива. Например,
```php
// использовать явное перечисление всех полей, лучше всего тогда, когда вы хотите убедиться,
// что изменения в вашей таблице базы данных или атрибуте модели не вызывают изменение вашего поля
// (для поддержания обратной совместимости 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`.
## Лучшие практические методики разработки моделей <span id="best-practices"></span>
Модели являются центральным местом представления бизнес-данных, правил и логики. Они часто повторно используются в разных местах. В хорошо спроектированном приложении, модели, как правило, намного больше, чем [контроллеры](structure-controllers.md).
В целом, модели
* могут содержать атрибуты для представления бизнес-данных;
* могут содержать правила проверки для обеспечения целостности и достоверности данных;
* могут содержать методы с реализацией бизнес-логики;
* не следует напрямую задавать запрос на доступ, либо сессии, либо любые другие данные об окружающей среде. Эти данные должны быть введены [контроллерами](structure-controllers.md) в модели;
* следует избегать встраивания HTML или другого отображаемого кода - это лучше делать в [видах](structure-views.md);
* избегайте слишком большого количества [сценариев](#scenarios) в одной модели.
Рекомендации выше обычно учитываются при разработке больших сложных систем. В таких системах, модели могут быть очень большими, в связи стем, что они используются во многих местах и поэтому могут содержать множество наборов правил и бизнес-логики. Это часто заканчивается кошмаром при поддержании кода модели, поскольку одним касанием кода можно повлиять на несколько разных мест. Чтобы сделать код модели более легким в обслуживании, Вы можете предпринять следующую стратегию:
* Определить набор базовых классов моделей, которые являются общими для разных [приложений](structure-applications.md) или [модулей](structure-modules.md). Эти классы моделей должны содержать минимальный набор правил и логики, которые являются общими среди всех используемых приложений или модулей.
* В каждом [приложении](structure-applications.md) или [модуле](structure-modules.md) в котором используется модель, определить конкретный класс модели (или классы моделей), отходящий от соответствующего базового класса модели. Конкретный класс модели должен содержать правила и логику, которые являются специфическими для данного приложения или модуля.
Например, в [шаблоне приложения advanced](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md), Вы можете определить базовым классом модели `common\models\Post`. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели `frontend\models\Post`, который расширяется от `common\models\Post`. И аналогичным образом для backend приложения, Вы определяете `backend\models\Post`. С помощью такой стратегии, можно быть уверенным, что код в `frontend\models\Post` используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение.