model.md 12.3 KB
Newer Older
Alexander Makarov committed
1 2 3
Model
=====

Alexander Makarov committed
4 5
In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data.
Yii models have the following basic features:
Alexander Makarov committed
6

Larry Ullman committed
7 8 9 10
- 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
11

Alexander Makarov committed
12 13 14 15 16 17
Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define
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
18 19 20 21

Attributes
----------

22 23 24
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
25 26

```php
Carsten Brandt committed
27
$post = new Post;
Alexander Makarov committed
28
$post->title = 'Hello, world';
Alexander Makarov committed
29
$post->content = 'Something interesting is happening.';
Alexander Makarov committed
30 31 32 33
echo $post->title;
echo $post->content;
```

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

```php
Carsten Brandt committed
38
$post = new Post;
Alexander Makarov committed
39 40 41 42 43 44
$post['title'] = 'Hello, world';
$post['content'] = 'Something interesting is happening';
echo $post['title'];
echo $post['content'];
```

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

```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
	public $username;
	public $password;
}
```

58 59
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
60
that is associated with the class.
Alexander Makarov committed
61 62


Qiang Xue committed
63
Attribute Labels
Carsten Brandt committed
64
----------------
Alexander Makarov committed
65 66

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

Alexander Makarov committed
70 71 72 73 74
To declare attribute labels, override the [[\yii\base\Model::attributeLabels()]] method. The overridden method returns
a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found
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`,
`orderNumber` to `Order Number`).
Alexander Makarov committed
75 76 77 78 79 80 81 82 83 84

```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
	public $username;
	public $password;

	public function attributeLabels()
	{
Alexander Makarov committed
85
		return [
Carsten Brandt committed
86
			'username' => 'Your name',
Alexander Makarov committed
87
			'password' => 'Your password',
Alexander Makarov committed
88
		];
Alexander Makarov committed
89 90 91 92 93 94 95
	}
}
```

Scenarios
---------

96
A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs,
Alexander Makarov committed
97 98
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.
99 100 101 102

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
103 104 105

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,
106
additional attributes such as `email` are *active*. When an attribute is *active* this means that it is subject to validation.
Alexander Makarov committed
107

108 109
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
110 111 112 113 114 115

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

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

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
If you want to keep the default scenario available besides your own scenarios, use inheritance to include it:
```php
class User extends \yii\db\ActiveRecord
{
	public function scenarios()
	{
		$scenarios = parent::scenarios();
		$scenarios['login'] = ['username', 'password'];
		$scenarios['register'] = ['username', 'email', 'password'];
		return $scenarios;
	}
}
```


142 143
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
144 145

```php
Alexander Makarov committed
146
['username', 'password', '!secret']
Alexander Makarov committed
147 148
```

149 150 151
In this example `username`, `password` and `secret` are *active* attributes but only `username` and `password` are
considered safe for massive assignment.

152
Identifying the active model scenario can be done using one of the following approaches:
153 154 155 156 157 158 159

```php
class EmployeeController extends \yii\web\Controller
{
	public function actionCreate($id = null)
	{
		// first way
Alexander Makarov committed
160
		$employee = new Employee(['scenario' => 'managementPanel']);
161 162 163 164 165 166

		// second way
		$employee = new Employee;
		$employee->scenario = 'managementPanel';

		// third way
Alexander Makarov committed
167
		$employee = Employee::find()->where('id = :id', [':id' => $id])->one();
168
		if ($employee !== null) {
169
			$employee->scenario = 'managementPanel';
170 171 172 173 174
		}
	}
}
```

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

180

Alexander Makarov committed
181 182 183 184 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
$model = new LoginForm;
$model->username = $_POST['username'];
$model->password = $_POST['password'];
if ($model->validate()) {
Carsten Brandt committed
194
	// ... login the user ...
Alexander Makarov committed
195 196
} else {
	$errors = $model->getErrors();
Carsten Brandt committed
197
	// ... display the errors to the end user ...
Alexander Makarov committed
198 199 200 201 202 203 204 205
}
```

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
Alexander Makarov committed
206
[
207
	['attribute1', 'attribute2', ...],
Alexander Makarov committed
208 209 210
	'validator class or alias',
	// specifies in which scenario(s) this rule is active.
	// if not given, it means it is active in all scenarios
211
	'on' => ['scenario1', 'scenario2', ...],
Alexander Makarov committed
212
	// the following name-value pairs will be used
Carsten Brandt committed
213 214 215 216
	// to initialize the validator properties
	'property1' => 'value1',
	'property2' => 'value2',
	// ...
Alexander Makarov committed
217
]
Alexander Makarov committed
218 219 220 221
```

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

Carsten Brandt committed
222 223
- the rule must be associated with at least one active attribute;
- the rule must be active for the current scenario.
Alexander Makarov committed
224 225 226 227 228 229 230 231 232 233


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
234 235 236 237 238
$post = Post::find(42);
if ($post) {
	$attributes = $post->attributes;
	var_dump($attributes);
}
Alexander Makarov committed
239 240 241 242 243
```

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

```php
Alexander Makarov committed
244
$post = new Post();
Alexander Makarov committed
245
$attributes = [
246
	'title' => 'Massive assignment example',
Alexander Makarov committed
247
	'content' => 'Never allow assigning attributes that are not meant to be assigned.',
Alexander Makarov committed
248
];
Alexander Makarov committed
249 250
$post->attributes = $attributes;
var_dump($attributes);
Alexander Makarov committed
251 252
```

Carsten Brandt committed
253
In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass
Alexander Makarov committed
254 255 256
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
257

Alexander Makarov committed
258 259 260 261 262 263 264 265
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
266
class User extends ActiveRecord
Alexander Makarov committed
267
{
268
	public function rules()
Alexander Makarov committed
269 270 271 272 273 274 275 276 277 278 279 280
	{
		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'],
		];
	}

281
	public function scenarios()
Alexander Makarov committed
282 283 284 285 286 287 288
	{
		return [
			// on signup allow mass assignment of username
			'signup' => ['username', 'password'],
			'update' => ['username', 'first_name'],
		];
	}
Alexander Makarov committed
289
}
Alexander Makarov committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
```

For the code above mass assignment will be allowed stsrictly according to `scenarios()`:

```php
$user = User::find(42);
$data = ['password' => '123'];
$user->attributes = $data;
print_r($data);
```

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

```php
$user = User::find(42);
$user->scenario = 'signup';
$data = [
	'username' => 'samdark',
	'password' => '123',
	'hashcode' => 'test',
];
$user->attributes = $data;
print_r($data);
```

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:
Alexander Makarov committed
327

Alexander Makarov committed
328 329
```php
class User extends ActiveRecord
Alexander Makarov committed
330
{
331
	public function rules()
Alexander Makarov committed
332 333 334 335 336 337 338
	{
		return [
			['username', 'string', 'length' => [4, 32]],
			['first_name', 'string', 'max' => 128],
			['password', 'required'],
		];
	}
Alexander Makarov committed
339 340 341
}
```

Alexander Makarov committed
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
The code above assumes default scenario so mass assignment will be available for all fields with `rules` defined:

```php
$user = User::find(42);
$data = [
	'username' => 'samdark',
	'first_name' => 'Alexander',
	'last_name' => 'Makarov',
	'password' => '123',
];
$user->attributes = $data;
print_r($data);
```

Will give you the following:
Alexander Makarov committed
357

Alexander Makarov committed
358 359 360 361 362 363 364
```php
array(
	'username' => 'samdark',
	'first_name' => 'Alexander',
	'password' => '123',
)
```
Carsten Brandt committed
365

Alexander Makarov committed
366
If you want some fields to be unsafe for default scenario:
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

```php
class User extends ActiveRecord
{
	function rules()
	{
		return [
			['username', 'string', 'length' => [4, 32]],
			['first_name', 'string', 'max' => 128],
			['password', 'required'],
		];
	}

	public function scenarios()
	{
		return [
Alexander Makarov committed
383
			self::SCENARIO_DEFAULT => ['username', 'first_name', '!password']
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
		];
	}
}
```

Mass assignment is still available by default:

```php
$user = User::find(42);
$data = [
	'username' => 'samdark',
	'first_name' => 'Alexander',
	'password' => '123',
];
$user->attributes = $data;
Alexander Makarov committed
399
print_r($user->attributes);
400 401 402 403 404 405 406 407 408 409 410 411
```

The code above gives you:

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

Alexander Makarov committed
412 413 414 415 416
See also
--------

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