Модели

Модели являются частью архитектуры MVC. Это объекты, представляющие бизнес-данные, правила и логику. Вы можете создавать классы моделей, расширяя yii\base\Model или его дочерние классы. Базовый класс yii\base\Model поддерживает множество полезных функций:

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

Класс Model также является базовым классом для более продвинутых моделей, таких как Active Record.

Атрибуты

Модели представляют бизнес-данные в терминах атрибутов. Каждый атрибут подобен общедоступному свойству модели. Метод 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'];

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

Объявление атрибутов

По умолчанию, если ваш класс модели распространяется непосредственно от yii\base\Model, все его нестатические переменные-члены являются атрибутами. Например, класс модели ContactForm, приведенный ниже, имеет четыре атрибута: name, address, 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(). Он переведет имена переменных в CamelCase стиль, при котором несколько слов будут написаны слитно без пробелов, при этом каждое слово пишется с заглавной буквы (в верхнем регистре). Например, username становится Username, а firstName становится FirstName.

Если вы не хотите использовать автоматически создаваемые ярлыки, вы можете переопределить 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'),
    ];
}

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

Сценарии (Scenarios)

Модель может использоваться в разных сценариях. Например, модель 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'],
        ];
    }
}

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

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

namespace app\models;

use yii\db\ActiveRecord;

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

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

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

Валидация правил

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

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

$model = new \app\models\ContactForm;

// Заполнять атрибуты модели пользовательскими данными
$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'],
    ];
}

Правило может использоваться для проверки одного или нескольких атрибутов, а атрибут может быть проверен одним или несколькими правилами. Иногда вы можете захотеть применить правило только в определенных сценариях. Чтобы сделать это, вы можете указать свойство 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().

Массовое назначение

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

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

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

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

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

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

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

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

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

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

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

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

$model->secret = $secret;

То же самое можно сделать в методе rules():

public function rules()
{
    return [
        [['username', 'password', '!secret'], 'required', 'on' => 'login']
    ];
}

В этом случае атрибуты username, password и secret необходимы, но secret должен быть явно задан.

Экспорт данных

Модели часто необходимо экспортировать в разных форматах. Например, вы можете преобразовать коллекцию моделей в формат JSON или Excel. Процесс экспорта можно разбить на два независимых шага:

  • Модели преобразуются в массивы;
  • Массивы преобразуются в целевые форматы.

Вы можете просто сосредоточиться на первом шаге, потому что второй шаг может быть достигнут универсальными форматерами данных, такими как yii\web\JsonResponseFormatter. Самый простой способ преобразования модели в массив - использовать свойство yii\base\Model::$attributes. Например:

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

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

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

Поля

Поле - это просто именованный элемент в массиве, который получается вызовом метода yii\base\Model::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;
}