structure-models.md 21 KB
Newer Older
Qiang Xue committed
1 2
Models
======
Alexander Makarov committed
3

Qiang Xue committed
4 5
Models are part of the [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) architecture.
They are objects representing business data, rules and logic.
Qiang Xue committed
6

Qiang Xue committed
7 8
You can create model classes by extending [[yii\base\Model]] or its child classes. The base class
[[yii\base\Model]] supports many useful features:
Larry Ullman committed
9

10 11 12 13
* [Attributes](#attributes): represent the business data and can be accessed like normal object properties
  or array elements;
* [Attribute labels](#attribute-labels): specify the display labels for attributes;
* [Massive assignment](#massive-assignment): supports populating multiple attributes in a single step;
Qiang Xue committed
14
* [Validation rules](#validation-rules): ensures input data based on the declared validation rules;
15
* [Data Exporting](#data-exporting): allows model data to be exported in terms of arrays with customizable formats.
Alexander Makarov committed
16

Qiang Xue committed
17 18
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.
Alexander Makarov committed
19

Qiang Xue committed
20
> Info: You are not required to base your model classes on [[yii\base\Model]]. However, because there are many Yii
21
  components built to support [[yii\base\Model]], it is usually the preferable base class for a model.
Alexander Makarov committed
22

Alexander Makarov committed
23

24
## Attributes <a name="attributes"></a>
Alexander Makarov committed
25

Qiang Xue committed
26 27
Models represent business data in terms of *attributes*. Each attribute is like a publicly accessible property
of a model. The method [[yii\base\Model::attributes()]] specifies what attributes a model class has.
Qiang Xue committed
28

Qiang Xue committed
29
You can access an attribute like accessing a normal object property:
Alexander Makarov committed
30 31

```php
Qiang Xue committed
32
$model = new \app\models\ContactForm;
Qiang Xue committed
33 34

// "name" is an attribute of ContactForm
Qiang Xue committed
35 36
$model->name = 'example';
echo $model->name;
Alexander Makarov committed
37 38
```

Qiang Xue committed
39 40 41
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]]:
Alexander Makarov committed
42 43

```php
Qiang Xue committed
44 45 46 47 48 49 50 51 52
$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";
Alexander Makarov committed
53 54 55
}
```

56

Qiang Xue committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
### Defining Attributes <a name="defining-attributes"></a>

By default, if your model class extends directly from [[yii\base\Model]], 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.

```php
namespace app\models;

use yii\base\Model;

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


You may override [[yii\base\Model::attributes()]] to define attributes in a different way. The method should
return the names of the attributes in a model. For example, [[yii\db\ActiveRecord]] 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.
Qiang Xue committed
83

Alexander Makarov committed
84

Qiang Xue committed
85
### Attribute Labels <a name="attribute-labels"></a>
Alexander Makarov committed
86

Qiang Xue committed
87 88 89
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.
Alexander Makarov committed
90

Qiang Xue committed
91 92 93 94 95 96 97 98 99
You can get the label of an attribute by calling [[yii\base\Model::getAttributeLabel()]]. For example,

```php
$model = new \app\models\ContactForm;

// displays "Label"
echo $model->getAttributeLabel('name');
```

Qiang Xue committed
100 101 102 103
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`.
Alexander Makarov committed
104

Qiang Xue committed
105 106
If you do not want to use automatically generated labels, you may override [[yii\base\Model::attributeLabels()]]
to explicitly declare attribute labels. For example,
Alexander Makarov committed
107 108

```php
Qiang Xue committed
109 110 111 112 113
namespace app\models;

use yii\base\Model;

class ContactForm extends Model
Alexander Makarov committed
114
{
Qiang Xue committed
115 116 117 118
    public $name;
    public $email;
    public $subject;
    public $body;
119 120 121 122

    public function attributeLabels()
    {
        return [
Qiang Xue committed
123 124 125 126
            'name' => 'Your name',
            'email' => 'Your email address',
            'subject' => 'Subject',
            'body' => 'Content',
127 128
        ];
    }
Alexander Makarov committed
129 130 131
}
```

Qiang Xue committed
132 133
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:
Alexander Makarov committed
134

Qiang Xue committed
135 136 137 138 139 140 141 142 143 144 145 146
```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'),
    ];
}
```

147 148 149
You may even conditionally define attribute labels. For example, based on the [scenario](#scenarios) the model
is being used in, you may return different labels for the same attribute.

Qiang Xue committed
150 151
> 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.
152

Alexander Makarov committed
153

154
## Scenarios <a name="scenarios"></a>
Alexander Makarov committed
155

Qiang Xue committed
156 157 158 159 160 161
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.
162 163 164 165 166 167 168 169 170 171 172
By default, a model supports only a single scenario named `default`. The following code shows two ways of
setting the scenario of a model:

```php
// scenario is set as a property
$model = new User;
$model->scenario = 'login';

// scenario is set through configuration
$model = new User(['scenario' => 'login']);
```
Qiang Xue committed
173

Qiang Xue committed
174
By default, the scenarios supported by a model are determined by the [validation rules](#validation-rules) declared
Qiang Xue committed
175
in the model. However, you can customize this behavior by overriding the [[yii\base\Model::scenarios()]] method,
176
like the following:
Alexander Makarov committed
177 178

```php
Qiang Xue committed
179 180 181 182 183
namespace app\models;

use yii\db\ActiveRecord;

class User extends ActiveRecord
Alexander Makarov committed
184
{
185 186 187 188 189 190 191
    public function scenarios()
    {
        return [
            'login' => ['username', 'password'],
            'register' => ['username', 'email', 'password'],
        ];
    }
Alexander Makarov committed
192 193 194
}
```

Qiang Xue committed
195 196 197 198 199
> 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
Qiang Xue committed
200
to [validation](#validation-rules). In the above example, the `username` and `password` attributes are active
Qiang Xue committed
201
in the `login` scenario; while in the `register` scenario, `email` is also active besides `username` and `password`.
Alexander Makarov committed
202

Qiang Xue committed
203 204 205
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:
206

207
```php
Qiang Xue committed
208 209 210 211 212
namespace app\models;

use yii\db\ActiveRecord;

class User extends ActiveRecord
213
{
214 215 216 217 218 219 220
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios['login'] = ['username', 'password'];
        $scenarios['register'] = ['username', 'email', 'password'];
        return $scenarios;
    }
221 222 223
}
```

Qiang Xue committed
224
The scenario feature is primarily used by [validation](#validation-rules) and [massive attribute assignment](#massive-assignment).
Qiang Xue committed
225 226
You can, however, use it for other purposes. For example, you may declare [attribute labels](#attribute-labels)
differently based on the current scenario.
227

228

Qiang Xue committed
229
## Validation Rules <a name="validation-rules"></a>
Alexander Makarov committed
230

231 232 233 234 235 236
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.

Qiang Xue committed
237 238 239 240
You may call [[yii\base\Model::validate()]] to validate the received data. The method will use
the validation rules declared in [[yii\base\Model::rules()]] to validate every relevant attribute. If no error
is found, it will return true. Otherwise, it will keep the errors in the [[yii\base\Model::errors]] property
and return false. For example,
Alexander Makarov committed
241 242

```php
243 244 245 246 247
$model = new \app\models\ContactForm;

// populate model attributes with user inputs
$model->attributes = \Yii::$app->request->post('ContactForm');

Alexander Makarov committed
248
if ($model->validate()) {
249
    // all inputs are valid
Alexander Makarov committed
250
} else {
251 252
    // validation failed: $errors is an array containing error messages
    $errors = $model->errors;
Alexander Makarov committed
253 254 255 256
}
```


257
To declare validation rules associated with a model, override the [[yii\base\Model::rules()]] method by returning
Qiang Xue committed
258
the rules that the model attributes should satisfy. The following example shows the validation rules declared
259
for the `ContactForm` model:
260 261 262 263

```php
public function rules()
{
264
    return [
265 266 267 268 269
        // 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'],
270
    ];
271 272 273
}
```

Qiang Xue committed
274 275 276
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](input-validation.md) section for more details on how to declare
validation rules.
277

Qiang Xue committed
278 279
Sometimes, you may want a rule to be applied only in certain [scenarios](#scenarios). To do so, you can
specify the `on` property of a rule, like the following:
280

Qiang Xue committed
281 282 283 284 285 286
```php
public function rules()
{
    return [
        // username, email and password are all required in "register" scenario
        [['username', 'email', 'password'], 'required', 'on' => 'register'],
Evgeniy Tkachenko committed
287

Qiang Xue committed
288 289 290 291 292
        // username and password are required in "login" scenario
        [['username', 'password'], 'required', 'on' => 'login'],
    ];
}
```
Evgeniy Tkachenko committed
293

Qiang Xue committed
294 295
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 [[yii\base\Model::scenario|scenario]].
Alexander Makarov committed
296

Qiang Xue committed
297 298 299
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()`.

Alexander Makarov committed
300

301
## Massive Assignment <a name="massive-assignment"></a>
Alexander Makarov committed
302

303 304 305 306 307
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 [[yii\base\Model::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:
Alexander Makarov committed
308 309

```php
310 311
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
Alexander Makarov committed
312 313
```

314 315 316 317 318 319 320 321
```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;
```
Alexander Makarov committed
322

Carsten Brandt committed
323

324
### Safe Attributes <a name="safe-attributes"></a>
Qiang Xue committed
325

326 327 328 329 330
Massive assignment only applies to the so-called *safe attributes* which are the attributes listed in
[[yii\base\Model::scenarios()]] for the current [[yii\base\Model::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.
Qiang Xue committed
331 332

```php
333 334 335 336 337 338 339
public function scenarios()
{
    return [
        'login' => ['username', 'password'],
        'register' => ['username', 'email', 'password'],
    ];
}
Qiang Xue committed
340 341
```

342 343 344 345
> 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 the `User` model
  has a `permission` attribute which determines the permission assigned to the user, you would
  like this attribute to be modifiable by administrators through a backend interface only.
Qiang Xue committed
346

347 348 349 350 351 352 353
Because the default implementation of [[yii\base\Model::scenarios()]] will return all scenarios and attributes
found in [[yii\base\Model::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.
Qiang Xue committed
354 355

```php
356
public function rules()
Qiang Xue committed
357
{
358 359 360
    return [
        [['title', 'description'], 'safe'],
    ];
Qiang Xue committed
361 362 363 364
}
```


365
### Unsafe Attributes <a name="unsafe-attributes"></a>
Qiang Xue committed
366

367 368 369 370
As described above, the [[yii\base\Model::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:
Alexander Makarov committed
371 372

```php
373
public function scenarios()
Alexander Makarov committed
374
{
375 376 377
    return [
        'login' => ['username', 'password', '!secret'],
    ];
Alexander Makarov committed
378
}
Alexander Makarov committed
379 380
```

381 382 383
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,
Alexander Makarov committed
384 385

```php
386
$model->secret = $secret;
Alexander Makarov committed
387 388 389
```


390 391 392 393 394 395 396
## Data Exporting <a name="data-exporting"></a>

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 [[yii\web\JsonResponseFormatter]].
Alexander Makarov committed
397

398 399
The simplest way of converting a model into an array is to use the [[yii\base\Model::attributes]] property.
For example,
Alexander Makarov committed
400 401

```php
402 403
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
Alexander Makarov committed
404 405
```

406 407
By default, the [[yii\base\Model::attributes]] property will return the values of *all* attributes
declared in [[yii\base\Model::attributes()]].
Alexander Makarov committed
408

409 410 411 412 413
A more flexible and powerful way of converting a model into an array is to use the [[yii\base\Model::toArray()]]
method. Its default behavior is the same as that of [[yii\base\Model::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](rest-response-formatting.md).
Alexander Makarov committed
414

Alexander Makarov committed
415

416
### Fields <a name="fields"></a>
Alexander Makarov committed
417

418 419 420 421 422 423 424 425 426 427
A field is simply a named element in the array that is obtained by calling the [[yii\base\Model::toArray()]] method
of a model.

By default, field names are equivalent to attribute names. However, you can change this behavior by overriding
the [[yii\base\Model::fields()|fields()]] and/or [[yii\base\Model::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()`.
Alexander Makarov committed
428

Alexander Makarov committed
429
```php
430
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Alexander Makarov committed
431
```
Carsten Brandt committed
432

433 434 435 436 437
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,
438 439

```php
440 441 442
// 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()
443
{
444 445 446
    return [
        // field name is the same as the attribute name
        'id',
447

448 449
        // field name is "email", the corresponding attribute name is "email_address"
        'email' => 'email_address',
450

451 452 453 454 455 456
        // field name is "name", its value is defined by a PHP callback
        'name' => function () {
            return $this->first_name . ' ' . $this->last_name;
        },
    ];
}
457

458 459 460 461 462
// 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();
463

464 465
    // remove fields that contain sensitive information
    unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
466

467 468
    return $fields;
}
469 470
```

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
> 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 override `fields()` to filter them out. In the above example, we choose
> to filter out `auth_key`, `password_hash` and `password_reset_token`.


## Best Practices <a name="best-practices"></a>

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](structure-controllers.md).

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](structure-controllers.md) into models;
* should avoid embedding HTML or other presentational code - this is better done in [views](structure-views.md);
* avoid having too many [scenarios](#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 mode code more maintainable,
you may take the following strategy:

* Define a set of base model classes that are shared by different [applications](structure-applications.md) or
  [modules](structure-modules.md). These model classes should contain minimal sets of rules and logic that
  are common among all their usages.
* In each [application](structure-applications.md) or [module](structure-modules.md) that uses a model,
  define a crete 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 Application Template](tutorial-advanced-app.md), you may define a base model
class `common\models\Post`. Then for the front end application, you define and use a concrete model class
`frontend\models\Post` which extends from `common\models\Post`. And similarly for the back end application,
you define `backend\models\Post`. With this strategy, you will be sure that the code in `frontend\models\Post`
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.