structure-models.md 15.9 KB
Newer Older
Alexander Makarov committed
1 2 3
Model
=====

4
> Note: This section is under development.
Qiang Xue committed
5

Larry Ullman committed
6 7
In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data, as well as defining the busines rules by which the data must abide.

Alexander Makarov committed
8
Yii models have the following basic features:
Alexander Makarov committed
9

Larry Ullman committed
10 11 12 13
- Attribute declaration: a model defines what is considered an attribute.
- Attribute labels: each attribute may be associated with a label for display purpose.
- Massive attribute assignment: the ability to populate multiple model attributes in one step.
- Scenario-based data validation.
Alexander Makarov committed
14

15
Models in Yii extend from the [[yii\base\Model]] class. Models are typically used to both hold data and define
Alexander Makarov committed
16 17 18 19 20
the validation rules for that data (aka, the business logic). The business logic greatly simplifies the generation
of models from complex web forms by providing validation and error reporting.

The Model class is also the base class for more advanced models with additional functionality, such
as [Active Record](active-record.md).
Alexander Makarov committed
21 22 23 24

Attributes
----------

25 26 27
The actual data represented by a model is stored in the model's *attributes*. Model attributes can
be accessed like the member variables of any object. For example, a `Post` model
may contain a `title` attribute and a `content` attribute, accessible as follows:
Alexander Makarov committed
28 29

```php
30
$post = new Post();
Alexander Makarov committed
31
$post->title = 'Hello, world';
Alexander Makarov committed
32
$post->content = 'Something interesting is happening.';
Alexander Makarov committed
33 34 35 36
echo $post->title;
echo $post->content;
```

37
Since [[yii\base\Model|Model]] implements the [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) interface,
38
you can also access the attributes as if they were array elements:
Alexander Makarov committed
39 40

```php
41
$post = new Post();
Alexander Makarov committed
42 43 44 45 46 47
$post['title'] = 'Hello, world';
$post['content'] = 'Something interesting is happening';
echo $post['title'];
echo $post['content'];
```

48
By default, [[yii\base\Model|Model]] requires that attributes be declared as *public* and *non-static*
Qiang Xue committed
49 50
class member variables. In the following example, the `LoginForm` model class declares two attributes:
`username` and `password`.
Alexander Makarov committed
51 52 53 54 55

```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
56 57
    public $username;
    public $password;
Alexander Makarov committed
58 59 60
}
```

61 62
Derived model classes may declare attributes in different ways, by overriding the [[yii\base\Model::attributes()|attributes()]]
method. For example, [[yii\db\ActiveRecord]] defines attributes using the column names of the database table
Qiang Xue committed
63
that is associated with the class.
Alexander Makarov committed
64 65


Qiang Xue committed
66
Attribute Labels
Carsten Brandt committed
67
----------------
Alexander Makarov committed
68 69

Attribute labels are mainly used for display purpose. For example, given an attribute `firstName`, we can declare
70
a label `First Name` that is more user-friendly when displayed to end users in places such as form labels and
71
error messages. Given an attribute name, you can obtain its label by calling [[yii\base\Model::getAttributeLabel()]].
Alexander Makarov committed
72

73
To declare attribute labels, override the [[yii\base\Model::attributeLabels()]] method. The overridden method returns
Alexander Makarov committed
74
a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found
75 76
in this mapping, its label will be generated using the [[yii\base\Model::generateAttributeLabel()]] method.
In many cases, [[yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`,
Alexander Makarov committed
77
`orderNumber` to `Order Number`).
Alexander Makarov committed
78 79 80 81 82

```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
83 84 85 86 87 88 89 90 91 92
    public $username;
    public $password;

    public function attributeLabels()
    {
        return [
            'username' => 'Your name',
            'password' => 'Your password',
        ];
    }
Alexander Makarov committed
93 94 95 96 97 98
}
```

Scenarios
---------

99
A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs,
Alexander Makarov committed
100 101
but it may also be used for user registration purposes. In the one scenario, every piece of data is required;
in the other, only the username and password would be.
102 103 104 105

To easily implement the business logic for different scenarios, each model has a property named `scenario`
that stores the name of the scenario that the model is currently being used in. As will be explained in the next
few sections, the concept of scenarios is mainly used for data validation and massive attribute assignment.
Alexander Makarov committed
106 107 108

Associated with each scenario is a list of attributes that are *active* in that particular scenario. For example,
in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario,
109
additional attributes such as `email` are *active*. When an attribute is *active* this means that it is subject to validation.
Alexander Makarov committed
110

111 112
Possible scenarios should be listed in the `scenarios()` method. This method returns an array whose keys are the scenario
names and whose values are lists of attributes that should be active in that scenario:
Alexander Makarov committed
113 114 115 116

```php
class User extends \yii\db\ActiveRecord
{
117 118 119 120 121 122 123
    public function scenarios()
    {
        return [
            'login' => ['username', 'password'],
            'register' => ['username', 'email', 'password'],
        ];
    }
Alexander Makarov committed
124 125 126
}
```

Alexander Makarov committed
127 128 129
If `scenarios` method is not defined, default scenario is applied. That means attributes with validation rules are
considered *active*.

130
If you want to keep the default scenario available besides your own scenarios, use inheritance to include it:
131

132 133 134
```php
class User extends \yii\db\ActiveRecord
{
135 136 137 138 139 140 141
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios['login'] = ['username', 'password'];
        $scenarios['register'] = ['username', 'email', 'password'];
        return $scenarios;
    }
142 143 144 145
}
```


146 147
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:
Alexander Makarov committed
148 149

```php
Alexander Makarov committed
150
['username', 'password', '!secret']
Alexander Makarov committed
151 152
```

153 154 155
In this example `username`, `password` and `secret` are *active* attributes but only `username` and `password` are
considered safe for massive assignment.

156
Identifying the active model scenario can be done using one of the following approaches:
157 158 159 160

```php
class EmployeeController extends \yii\web\Controller
{
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    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';
        }
    }
176 177 178
}
```

179
The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models,
Alexander Makarov committed
180 181 182
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.
183

184

Alexander Makarov committed
185 186 187 188 189 190 191 192 193
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
194
$model = new LoginForm();
Alexander Makarov committed
195 196 197
$model->username = $_POST['username'];
$model->password = $_POST['password'];
if ($model->validate()) {
198
    // ... login the user ...
Alexander Makarov committed
199
} else {
200 201
    $errors = $model->getErrors();
    // ... display the errors to the end user ...
Alexander Makarov committed
202 203 204 205 206
}
```

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
207
instance of a [[yii\validators\Validator]] child class, or an array with the following format:
Alexander Makarov committed
208 209

```php
Alexander Makarov committed
210
[
211 212 213 214 215 216 217 218 219 220
    ['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',
    // ...
Alexander Makarov committed
221
]
Alexander Makarov committed
222 223 224 225
```

When `validate()` is called, the actual validation rules executed are determined using both of the following criteria:

Carsten Brandt committed
226 227
- the rule must be associated with at least one active attribute;
- the rule must be active for the current scenario.
Alexander Makarov committed
228 229


230 231 232
### 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.
233 234
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.
235 236 237 238 239 240 241 242

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)
{
243 244 245 246
    $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.');
    }
247 248 249 250
}

public function rules()
{
251 252 253 254
    return [
        // ...
        [['birthdate'], 'validateAge', 'params' => ['min' => '12']],
    ];
255 256 257
}
```

258 259
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:
260 261 262 263 264

```php
[['birthdate'], 'validateAge', 'params' => ['min' => '12'], 'skipOnEmpty' => false],
```

265
### Conditional validation
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

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],
    ];
}
```

Evgeniy Tkachenko committed
293 294 295 296 297 298 299 300 301
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';}"
Evgeniy Tkachenko committed
302
    ];
Evgeniy Tkachenko committed
303 304 305 306 307 308 309
  
    return [
        ['state', 'required', 'when' => $usa['server-side'], 'whenClient' => $usa['client-side']],
    ];
}
```

310

Alexander Makarov committed
311 312 313 314 315 316 317 318
Massive Attribute Retrieval and 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
Alexander Makarov committed
319
$post = Post::findOne(42);
Alexander Makarov committed
320
if ($post) {
321 322
    $attributes = $post->attributes;
    var_dump($attributes);
Alexander Makarov committed
323
}
Alexander Makarov committed
324 325 326 327 328
```

Using the same `attributes` property you can massively assign data from associative array to model attributes:

```php
Alexander Makarov committed
329
$post = new Post();
Alexander Makarov committed
330
$attributes = [
331 332
    'title' => 'Massive assignment example',
    'content' => 'Never allow assigning attributes that are not meant to be assigned.',
Alexander Makarov committed
333
];
Alexander Makarov committed
334
$post->attributes = $attributes;
MetalGuardian committed
335
var_dump($post->attributes);
Alexander Makarov committed
336 337
```

Carsten Brandt committed
338
In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass
Alexander Makarov committed
339 340 341
retrieval that always works for all attributes is that in order to be assigned an attribute should be **safe** else
it will be ignored.

Carsten Brandt committed
342

Alexander Makarov committed
343 344 345 346 347 348 349 350
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
Alexander Makarov committed
351
class User extends ActiveRecord
Alexander Makarov committed
352
{
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
    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'],
        ];
    }
Alexander Makarov committed
374
}
Alexander Makarov committed
375 376
```

377
For the code above mass assignment will be allowed strictly according to `scenarios()`:
Alexander Makarov committed
378 379

```php
Alexander Makarov committed
380
$user = User::findOne(42);
Alexander Makarov committed
381 382
$data = ['password' => '123'];
$user->attributes = $data;
383
print_r($user->attributes);
Alexander Makarov committed
384 385 386 387 388
```

Will give you empty array because there's no default scenario defined in our `scenarios()`.

```php
Alexander Makarov committed
389
$user = User::findOne(42);
Alexander Makarov committed
390 391
$user->scenario = 'signup';
$data = [
392 393 394
    'username' => 'samdark',
    'password' => '123',
    'hashcode' => 'test',
Alexander Makarov committed
395 396
];
$user->attributes = $data;
397
print_r($user->attributes);
Alexander Makarov committed
398 399 400 401 402 403
```

Will give you the following:

```php
array(
404 405 406 407
    'username' => 'samdark',
    'first_name' => null,
    'password' => '123',
    'hashcode' => null, // it's not defined in scenarios method
Alexander Makarov committed
408 409 410 411
)
```

In case of not defined `scenarios` method like the following:
Alexander Makarov committed
412

Alexander Makarov committed
413 414
```php
class User extends ActiveRecord
Alexander Makarov committed
415
{
416 417 418 419 420 421 422 423
    public function rules()
    {
        return [
            ['username', 'string', 'length' => [4, 32]],
            ['first_name', 'string', 'max' => 128],
            ['password', 'required'],
        ];
    }
Alexander Makarov committed
424 425 426
}
```

Alexander Makarov committed
427 428 429
The code above assumes default scenario so mass assignment will be available for all fields with `rules` defined:

```php
Alexander Makarov committed
430
$user = User::findOne(42);
Alexander Makarov committed
431
$data = [
432 433 434 435
    'username' => 'samdark',
    'first_name' => 'Alexander',
    'last_name' => 'Makarov',
    'password' => '123',
Alexander Makarov committed
436 437
];
$user->attributes = $data;
438
print_r($user->attributes);
Alexander Makarov committed
439 440 441
```

Will give you the following:
Alexander Makarov committed
442

Alexander Makarov committed
443 444
```php
array(
445 446 447
    'username' => 'samdark',
    'first_name' => 'Alexander',
    'password' => '123',
Alexander Makarov committed
448 449
)
```
Carsten Brandt committed
450

Alexander Makarov committed
451
If you want some fields to be unsafe for default scenario:
452 453 454 455

```php
class User extends ActiveRecord
{
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
    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']
        ];
    }
471 472 473 474 475 476
}
```

Mass assignment is still available by default:

```php
Alexander Makarov committed
477
$user = User::findOne(42);
478
$data = [
479 480 481
    'username' => 'samdark',
    'first_name' => 'Alexander',
    'password' => '123',
482 483
];
$user->attributes = $data;
Alexander Makarov committed
484
print_r($user->attributes);
485 486 487 488 489 490
```

The code above gives you:

```php
array(
491 492 493
    'username' => 'samdark',
    'first_name' => 'Alexander',
    'password' => null, // because of ! before field name in scenarios
494 495 496
)
```

Alexander Makarov committed
497 498 499 500
See also
--------

- [Model validation reference](validation.md)
501
- [[yii\base\Model]]