# Модели ¶

Модели являются частью архитектуры MVC (Модель-Вид-Контроллер). Они представляют собой объекты бизнес данных, правил и логики.

Вы можете создавать классы моделей путём расширения класса yii\base\Model или его дочерних классов. Базовый класс yii\base\Model поддерживает много полезных функций:

- Атрибуты: представляют собой рабочие данные и могут быть доступны как обычные свойства объекта или элементы массива;
- Метки атрибутов: задают отображение атрибута;
- Массовое присвоение: поддержка заполнения нескольких атрибутов в один шаг;
- Правила проверки: обеспечивают ввод данных на основе заявленных правил проверки;
- Экспорт Данных: разрешает данным модели быть экспортированными в массивы с настройкой форматов.

Класс `Model` также является базовым классом для многих расширенных моделей, таких как Active Record. Пожалуйста, обратитесь к соответствующей документации для более подробной информации об этих расширенных моделях.

> Информация: Вы не обязаны основывать свои классы моделей на yii\base\Model. Однако, поскольку в yii есть много компонентов, созданных для поддержки yii\base\Model, обычно так делать предпочтительнее для базового класса модели.

## Атрибуты ¶

Модели предоставляют рабочие данные в терминах атрибутах. Каждый атрибут представляет собой публично доступное свойство модели. Метод yii\base\Model::attributes() определяет какие атрибуты имеет класс модели.

Вы можете получить доступ к атрибуту как к обычному свойству объекта:

    $model = new \app\models\ContactForm;

    // "name" - это атрибут модели ContactForm
    $model->name = 'example';
    echo $model->name;

Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке ArrayAccess и Traversable в yii\base\Model:

    $model = new \app\models\ContactForm;

    // доступ к атрибутам как к элементам массива
    $model['name'] = 'example';
    echo $model['name'];

    // Модель является обходимой(traversable) с использованием foreach.
    foreach ($model as $name => $value) {
        echo "$name: $value\n";
    }

### Определение Атрибутов ¶

По умолчанию, если ваш класс модели расширяется напрямую от yii\base\Model, то все не статичные публичные переменные являются атрибутами. Например, у класса модели `ContactForm` , который находится ниже, четыре атрибута: `name`, `email`, `subject` и `body`. Модель `ContactForm` используется для представления входных данных, полученных из HTML формы.

    namespace app\models;

    use yii\base\Model;

    class ContactForm extends Model
    {
        public $name;
        public $email;
        public $subject;
        public $body;
    }

Вы можете переопределить метод yii\base\Model::attributes(), чтобы определять атрибуты другим способом. Метод должен возвращать имена атрибутов в модели. Например yii\db\ActiveRecord делает так, возвращая имена столбцов из связанной таблицы базы данных в качестве имён атрибутов. Также может понадобиться переопределить магические методы, такие как `__get()`, `__set()` для того, чтобы атрибуты могли быть доступны как обычные свойства объекта.

### Метки атрибутов ¶

При отображении значений или при получении ввода значений атрибутов, часто требуется отобразить некоторые надписи, связанные с атрибутами. Например, если атрибут назван `firstName`, Вы можете отобразить его как `First Name`, что является более удобным для пользователя, в тех случаях, когда атрибут отображается конечным пользователям в таких местах, как форма входа и сообщения об ошибках.

Вы можете получить метку атрибута, вызвав yii\base\Model::getAttributeLabel(). Например,

    $model = new \app\models\ContactForm;

    // отобразит "Name"
    echo $model->getAttributeLabel('name');

По умолчанию, метки атрибутов автоматически генерируются из названия атрибута. Генерация выполняется методом yii\base\Model::generateAttributeLabel(). Он превращает первую букву каждого слова в верхний регистр, если имена переменных состоят из нескольких слов. Например, `username` станет `Username`, а `firstName` станет `First Name`.

Если Вы не хотите использовать автоматически сгенерированные метки, Вы можете переопределить метод yii\base\Model::attributeLabels(), чтобы явно объявить метку атрибута. Например,

    namespace app\models;

    use yii\base\Model;

    class ContactForm extends Model
    {
        public $name;
        public $email;
        public $subject;
        public $body;

        public function attributeLabels()
        {
            return [
                'name' => 'Your name',
                'email' => 'Your email address',
                'subject' => 'Subject',
                'body' => 'Content',
            ];
        }
    }

Для приложений поддерживающих мультиязычность, Вы можете перевести метки атрибутов. Это можно сделать в методе attributeLabels() как показано ниже:

    public function attributeLabels()
    {
        return [
            'name' => \Yii::t('app', 'Your name'),
            'email' => \Yii::t('app', 'Your email address'),
            'subject' => \Yii::t('app', 'Subject'),
            'body' => \Yii::t('app', 'Content'),
        ];
    }

Можно даже условно определять метки атрибутов. Например, на основе сценариев и использованной в нём модели , Вы можете возвращать различные метки для одного и того же атрибута.

> Для справки: Строго говоря, метки атрибутов являются частью видов. Но объявление меток в моделях часто очень удобно и приводит к чистоте кода и повторному его использованию.

## Сценарии ¶

Модель может быть использована в различных сценариях. Например, модель `User` может быть использована для коллекции входных логинов пользователей, а также может быть использована для цели регистрации пользователей. В различных сценариях, модель может использовать различные бизнес-правила и логику. Например, атрибут `email` может потребоваться во время регистрации пользователя, но не во время входа пользователя в систему.

Модель использует свойство yii\base\Model::$scenario, чтобы отслеживать сценарий, в котором она используется. По умолчанию, модель поддерживает только один сценарий с именем `default`. В следующем коде показано два способа установки сценария модели:

    // сценарий задается как свойство
    $model = new User;
    $model->scenario = User::SCENARIO_LOGIN;

    // сценарий задается через конфигурацию
    $model = new User(['scenario' => User::SCENARIO_LOGIN]);

По умолчанию сценарии, поддерживаемые моделью, определяются правилами валидации объявленными в модели. Однако Вы можете изменить это поведение путем переопределения метода yii\base\Model::scenarios() как показано ниже:

    namespace app\models;

    use yii\db\ActiveRecord;

    class User extends ActiveRecord
    {
        const SCENARIO_LOGIN = 'login';
        const SCENARIO_REGISTER = 'register';

        public function scenarios()
        {
            return [
                self::SCENARIO_LOGIN => ['username', 'password'],
                self::SCENARIO_REGISTER => ['username', 'email', 'password'],
            ];
        }
    }

> Информация: В приведенном выше и следующих примерах, классы моделей расширяются от yii\db\ActiveRecord потому, что использование нескольких сценариев обычно происходит от классов Active Record.

Метод `scenarios()` возвращает массив, ключами которого являются имена сценариев, а значения - соответствующие активные атрибуты. Активные атрибуты могут быть массово присвоены и подлежат валидации. В приведенном выше примере, атрибуты `username` и `password` это активные атрибуты сценария `login`, а в сценарии `register` так же активным атрибутом является `email` вместе с `username` и `password`.

По умолчанию реализация `scenarios()` вернёт все найденные сценарии в правилах валидации, задекларированных в методе yii\base\Model::rules(). При переопределении метода `scenarios()`, если Вы хотите ввести новые сценарии помимо стандартных, Вы можете написать код на основе следующего примера:

    namespace app\models;

    use yii\db\ActiveRecord;

    class User extends ActiveRecord
    {
        const SCENARIO_LOGIN = 'login';
        const SCENARIO_REGISTER = 'register';

        public function scenarios()
        {
            $scenarios = parent::scenarios();
            $scenarios[self::SCENARIO_LOGIN] = ['username', 'password'];
            $scenarios[self::SCENARIO_REGISTER] = ['username', 'email', 'password'];
            return $scenarios;
        }
    }

Возможности сценариев в основном используются валидацией и массовым присвоением атрибутов. Однако, Вы можете использовать их и для других целей. Например, Вы можете различным образом объявлять метки атрибутов на основе текущего сценария.

## Правила валидации ¶

Когда данные модели, получены от конечных пользователей, они должны быть проверены, для того чтобы убедиться, что данные удовлетворяют определенным правилам (так называемым правилам валидации также известными как бизнес-правила). Например, дана модель `ContactForm`, возможно Вы захотите убедиться, что все атрибуты являются не пустыми значениями, а атрибут `email` содержит допустимый адрес электронной почты. Если значения нескольких атрибутов не удовлетворяют соответствующим бизнес-правилам, то должны быть показаны соответствующие сообщения об ошибках, чтобы помочь конечному пользователю исправить допущенные ошибки.

Вы можете вызвать yii\base\Model::validate() для проверки полученных данных. Данный метод будет использовать правила валидации определённые в yii\base\Model::rules() для проверки каждого соответствующего атрибута. Если ошибок не найдено, то возвращается `true`, в противном случае возвращается `false`, а ошибки содержит свойство yii\base\Model::$errors. Например,

    $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`:

    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()`.

## Массовое Присвоение ¶

Массовое присвоение - это удобный способ заполнения модели данными вводимыми пользователем с помощью одной строки кода. Он заполняет атрибуты модели путем присвоения входных данных непосредственно свойству yii\base\Model::$attributes. Следующие два куска кода эквивалентны, они оба пытаются присвоить данные из формы представленные конечными пользователями атрибутам модели `ContactForm`. Ясно, что первый код гораздо чище и менее подвержен ошибкам, чем второй:

    $model = new \app\models\ContactForm;
    $model->attributes = \Yii::$app->request->post('ContactForm');

    $model = new \app\models\ContactForm;
    $data = \Yii::$app->request->post('ContactForm', []);
    $model->name = isset($data['name']) ? $data['name'] : null;
    $model->email = isset($data['email']) ? $data['email'] : null;
    $model->subject = isset($data['subject']) ? $data['subject'] : null;
    $model->body = isset($data['body']) ? $data['body'] : null;

### Безопасные Атрибуты ¶

Массовое присвоение применяется только к так называемым безопасным атрибутам, которые являются атрибутами, перечисленными в yii\base\Model::scenarios() в текущем сценарии scenario модели. Например, если модель `User` имеет следующий заданный сценарий, в данном случае это сценарий `login`, то только `username` и `password` могут быть массово присвоены. Любые другие атрибуты останутся нетронутыми.

    public function scenarios()
    {
        return [
            self::SCENARIO_LOGIN => ['username', 'password'],
            self::SCENARIO_REGISTER => ['username', 'email', 'password'],
        ];
    }

> Информация: Причиной того, что массовое присвоение атрибутов применяется только к безопасным атрибутам, является то, что необходимо контролировать какие атрибуты могут быть изменены конечными пользователями. Например, если модель `User` имеет атрибут `permission`, который определяет разрешения, назначенные пользователю, то необходимо быть уверенным, что данный атрибут может быть изменён только администраторами через бэкэнд-интерфейс.

По умолчанию yii\base\Model::scenarios() будет возвращать все сценарии и атрибуты найденные в yii\base\Model::rules(), если не переопределить этот метод, атрибут будет считаться безопасным только в случае, если он участвует в любом из активных правил проверки.

По этой причине существует специальный валидатор с псевдонимом `safe`, он предоставляет возможность объявить атрибут безопасным без фактической его проверки. Например, следующие правила определяют, что оба атрибута `title` и `description` являются безопасными атрибутами.

    public function rules()
    {
        return [
            [['title', 'description'], 'safe'],
        ];
    }

### Небезопасные атрибуты ¶

Как сказано выше, метод yii\base\Model::scenarios() служит двум целям: определения, какие атрибуты должны быть проверены, и определения, какие атрибуты являются безопасными (т.е. не требуют проверки). В некоторых случаях необходимо проверить атрибут не объявляя его безопасным. Вы можете сделать это с помощью префикса восклицательный знак `!` в имени атрибута при объявлении его в `scenarios()` как атрибут `secret` в следующем примере:

    public function scenarios()
    {
        return [
            self::SCENARIO_LOGIN => ['username', 'password', '!secret'],
        ];
    }

Когда модель будет присутствовать в сценарии `login`, то все три эти атрибута будут проверены. Однако, только атрибуты `username` и `password` могут быть массово присвоены. Назначить входное значение атрибуту `secret` нужно явно следующим образом,

    $model->secret = $secret;

## Экспорт Данных ¶

Часто нужно экспортировать модели в различные форматы. Например, может потребоваться преобразовать коллекцию моделей в JSON или Excel формат. Процесс экспорта может быть разбит на два самостоятельных шага. На первом этапе модели преобразуются в массивы; на втором этапе массивы преобразуются в целевые форматы. Вы можете сосредоточиться только на первом шаге потому, что второй шаг может быть достигнут путем универсального инструмента форматирования данных, такого как yii\web\JsonResponseFormatter.

Самый простой способ преобразования модели в массив - использовать свойство yii\base\Model::$attributes. Например

    $post = \app\models\Post::findOne(100);
    $array = $post->attributes;

По умолчанию свойство yii\base\Model::$attributes возвращает значения всех атрибутов объявленных в yii\base\Model::attributes().

Более гибкий и мощный способ конвертирования модели в массив - использовать метод yii\base\Model::toArray(). Его поведение по умолчанию такое же как и у yii\base\Model::$attributes. Тем не менее, он позволяет выбрать, какие элементы данных, называемые полями, поставить в результирующий массив и как они должны быть отформатированы. На самом деле, этот способ экспорта моделей по умолчанию применяется при разработке в RESTful Web service, как описано в Response Formatting.

### Поля ¶

Поле - это просто именованный элемент в массиве, который может быть получен вызовом метода yii\base\Model::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, Вы можете определить базовым классом модели `common\models\Post`. Тогда для frontend приложения, Вы определяете и используете конкретный класс модели `frontend\models\Post`, который расширяется от `common\models\Post`. И аналогичным образом для backend приложения, Вы определяете `backend\models\Post`. С помощью такой стратегии, можно быть уверенным, что код в `frontend\models\Post` используется только для конкретного frontend приложения, и если делаются любые изменения в нём, то не нужно беспокоиться, что изменения могут сломать backend приложение.