Models ====== Models are objects representing business data, rules and logic. They are part of the [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) architecture. You can create model classes by extending [[yii\base\Model]] or its child classes. The base class [[yii\base\Model]] supports many useful features: * Attributes: represent the business data; * Attribute labels: specify the display labels for attributes; * Massive attribute assignment: supports populating multiple attributes in a single step; * Data validation: validates input data based on the declared validation rules; * Data export: allows exporting model data in terms of arrays without customizable formats; * Array access: supports accessing model data like an associative array. The `Model` class is also the base class for more advanced models, such as [Active Record](db-active-record.md). Please refer to the relevant documentation for more details about these advanced models. > Info: You are not required to base your model classes on [[yii\base\Model]]. However, because there are many Yii components built to support [[yii\base\Model]], it is usually the preferable base model classes. ## Attributes Attributes are the properties that represent business data. By default, attributes are *non-static public* member variables if your model class extends directly from [[yii\base\Model]]. The following code creates a `ContactForm` model class with four attributes: `name`, `email`, `subject` and `body`. ```php namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; } ``` Naturally, you can access an attribute like accessing a normal object property: ```php $model = new \app\models\ContactForm; $model->name = 'example'; echo $model->name; ``` You can also access attributes like accessing array elements, thanks to the support for [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) and [ArrayIterator](http://php.net/manual/en/class.arrayiterator.php) by [[yii\base\Model]]: ```php $model = new \app\models\ContactForm; // accessing attributes like array elements $model['name'] = 'example'; echo $model['name']; // iterate attributes foreach ($model as $name => $value) { echo "$name: $value\n"; } ``` You may override [[yii\base\Model::attributes()]] if you want to support different ways of defining attributes. For example, [[yii\db\ActiveRecord]] does so and defines attributes according to table columns. 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. By default, attribute labels are automatically generated from attribute names. The generation is done by the method [[yii\base\Model::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 [[yii\base\Model::attributeLabels()]] to explicitly declare attribute labels. For example, ```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', ]; } } ``` For applications supporting multiple languages, you may want to translate attribute labels. This can be done in the [[yii\base\Model::attributeLabels()|attributeLabels()]] method as well, like the following: ```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'), ]; } ``` > Info: Strictly speaking, attribute labels are part of [views](structure-views.md). 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 [[yii\base\Model::scenario]] property to keep track of the scenario it is being used in. By default, a model supports only a single scenario named `default`. To support multiple scenarios, you may override the [[yii\base\Model::scenarios()]] method, like the following: ```php namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], ]; } } ``` > Info: In the above and following examples, the model classes are extending from [[yii\db\ActiveRecord]] because the usage of multiple scenarios usually happens to [Active Record](db-active-record.md) 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](#massive-assignment) and is subject to [validation](#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 [[yii\base\Model::rules()]]. When overriding `scenarios()`, if you want to introduce new scenarios in addition to the default ones, you may write code like the following: ```php namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { public function scenarios() { $scenarios = parent::scenarios(); $scenarios['login'] = ['username', 'password']; $scenarios['register'] = ['username', 'email', 'password']; return $scenarios; } } ``` The scenario feature is primarily used by [validation](#validation) and [massive attribute assignment](#massive-assignment). You can, however, use it for other purposes. For example, you may declare [attribute labels](#attribute-labels) differently based on the current scenario. ## Validation When a model is used to collect user input data via its attributes, it usually needs to validate the affected attributes to make sure they satisfy certain requirements, such as an attribute cannot be empty, an attribute must contain letters only, etc. If errors are found in validation, they may be presented to the user to help him fix the errors. The following example shows how the validation is performed: ```php $model = new LoginForm(); $model->username = $_POST['username']; $model->password = $_POST['password']; if ($model->validate()) { // ... login the user ... } else { $errors = $model->getErrors(); // ... display the errors to the end user ... } ``` The possible validation rules for a model should be listed in its `rules()` method. Each validation rule applies to one or several attributes and is effective in one or several scenarios. A rule can be specified using a validator object - an instance of a [[yii\validators\Validator]] child class, or an array with the following format: ```php [ ['attribute1', 'attribute2', ...], 'validator class or alias', // specifies in which scenario(s) this rule is active. // if not given, it means it is active in all scenarios 'on' => ['scenario1', 'scenario2', ...], // the following name-value pairs will be used // to initialize the validator properties 'property1' => 'value1', 'property2' => 'value2', // ... ] ``` When `validate()` is called, the actual validation rules executed are determined using both of the following criteria: - the rule must be associated with at least one active attribute; - the rule must be active for the current scenario. ### Creating your own validators (Inline validators) If none of the built in validators fit your needs, you can create your own validator by creating a method in you model class. This method will be wrapped by an [[yii\validators\InlineValidator|InlineValidator]] an be called upon validation. You will do the validation of the attribute and [[yii\base\Model::addError()|add errors]] to the model when validation fails. The method has the following signature `public function myValidator($attribute, $params)` while you are free to choose the name. Here is an example implementation of a validator validating the age of a user: ```php public function validateAge($attribute, $params) { $value = $this->$attribute; if (strtotime($value) > strtotime('now - ' . $params['min'] . ' years')) { $this->addError($attribute, 'You must be at least ' . $params['min'] . ' years old to register for this service.'); } } public function rules() { return [ // ... [['birthdate'], 'validateAge', 'params' => ['min' => '12']], ]; } ``` You may also set other properties of the [[yii\validators\InlineValidator|InlineValidator]] in the rules definition, for example the [[yii\validators\InlineValidator::$skipOnEmpty|skipOnEmpty]] property: ```php [['birthdate'], 'validateAge', 'params' => ['min' => '12'], 'skipOnEmpty' => false], ``` ### Conditional validation To validate attributes only when certain conditions apply, e.g. the validation of one field depends on the value of another field you can use [[yii\validators\Validator::when|the `when` property]] to define such conditions: ```php ['state', 'required', 'when' => function($model) { return $model->country == Country::USA; }], ['stateOthers', 'required', 'when' => function($model) { return $model->country != Country::USA; }], ['mother', 'required', 'when' => function($model) { return $model->age < 18 && $model->married != true; }], ``` For better readability the conditions can also be written like this: ```php public function rules() { $usa = function($model) { return $model->country == Country::USA; }; $notUsa = function($model) { return $model->country != Country::USA; }; $child = function($model) { return $model->age < 18 && $model->married != true; }; return [ ['state', 'required', 'when' => $usa], ['stateOthers', 'required', 'when' => $notUsa], // note that it is not possible to write !$usa ['mother', 'required', 'when' => $child], ]; } ``` When you need conditional validation logic on client-side (`enableClientValidation` is true), don't forget to add `whenClient`: ```php public function rules() { $usa = [ 'server-side' => function($model) { return $model->country == Country::USA; }, 'client-side' => "function (attribute, value) {return $('#country').value == 'USA';}" ]; return [ ['state', 'required', 'when' => $usa['server-side'], 'whenClient' => $usa['client-side']], ]; } ``` ## Massive Attribute Assignment Attributes can be massively retrieved via the `attributes` property. The following code will return *all* attributes in the `$post` model as an array of name-value pairs. ```php $post = Post::findOne(42); if ($post) { $attributes = $post->attributes; var_dump($attributes); } ``` Using the same `attributes` property you can massively assign data from associative array to model attributes: ```php $post = new Post(); $attributes = [ 'title' => 'Massive assignment example', 'content' => 'Never allow assigning attributes that are not meant to be assigned.', ]; $post->attributes = $attributes; var_dump($post->attributes); ``` In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass retrieval that always works for all attributes is that in order to be assigned an attribute should be **safe** else it will be ignored. Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want the attribute to be validated). We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example: ```php ['username', 'password', '!secret'] ``` In this example `username`, `password` and `secret` are *active* attributes but only `username` and `password` are considered safe for massive assignment. Identifying the active model scenario can be done using one of the following approaches: ```php class EmployeeController extends \yii\web\Controller { public function actionCreate($id = null) { // first way $employee = new Employee(['scenario' => 'managementPanel']); // second way $employee = new Employee(); $employee->scenario = 'managementPanel'; // third way $employee = Employee::find()->where('id = :id', [':id' => $id])->one(); if ($employee !== null) { $employee->scenario = 'managementPanel'; } } } ``` The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models, scenarios are rarely needed, as the basic form model is normally tied directly to a single form and, as noted above, the default implementation of the `scenarios()` returns every property with active validation rule making it always available for mass assignment and validation. Validation rules and mass assignment ------------------------------------ In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation rules are described in `rules()` method of the model while what's safe for mass assignment is described in `scenarios` method: ```php class User extends ActiveRecord { public function rules() { return [ // rule applied when corresponding field is "safe" ['username', 'string', 'length' => [4, 32]], ['first_name', 'string', 'max' => 128], ['password', 'required'], // rule applied when scenario is "signup" no matter if field is "safe" or not ['hashcode', 'check', 'on' => 'signup'], ]; } public function scenarios() { return [ // on signup allow mass assignment of username 'signup' => ['username', 'password'], 'update' => ['username', 'first_name'], ]; } } ``` For the code above mass assignment will be allowed strictly according to `scenarios()`: ```php $user = User::findOne(42); $data = ['password' => '123']; $user->attributes = $data; print_r($user->attributes); ``` Will give you empty array because there's no default scenario defined in our `scenarios()`. ```php $user = User::findOne(42); $user->scenario = 'signup'; $data = [ 'username' => 'samdark', 'password' => '123', 'hashcode' => 'test', ]; $user->attributes = $data; print_r($user->attributes); ``` Will give you the following: ```php array( 'username' => 'samdark', 'first_name' => null, 'password' => '123', 'hashcode' => null, // it's not defined in scenarios method ) ``` In case of not defined `scenarios` method like the following: ```php class User extends ActiveRecord { public function rules() { return [ ['username', 'string', 'length' => [4, 32]], ['first_name', 'string', 'max' => 128], ['password', 'required'], ]; } } ``` The code above assumes default scenario so mass assignment will be available for all fields with `rules` defined: ```php $user = User::findOne(42); $data = [ 'username' => 'samdark', 'first_name' => 'Alexander', 'last_name' => 'Makarov', 'password' => '123', ]; $user->attributes = $data; print_r($user->attributes); ``` Will give you the following: ```php array( 'username' => 'samdark', 'first_name' => 'Alexander', 'password' => '123', ) ``` If you want some fields to be unsafe for default scenario: ```php class User extends ActiveRecord { function rules() { return [ ['username', 'string', 'length' => [4, 32]], ['first_name', 'string', 'max' => 128], ['password', 'required'], ]; } public function scenarios() { return [ self::SCENARIO_DEFAULT => ['username', 'first_name', '!password'] ]; } } ``` Mass assignment is still available by default: ```php $user = User::findOne(42); $data = [ 'username' => 'samdark', 'first_name' => 'Alexander', 'password' => '123', ]; $user->attributes = $data; print_r($user->attributes); ``` The code above gives you: ```php array( 'username' => 'samdark', 'first_name' => 'Alexander', 'password' => null, // because of ! before field name in scenarios ) ``` ## Data Exporting ## Best Practices scenarios validation rules labels