Commit bb93ccf5 by Qiang Xue

finished tutorial on models [skip ci]

parent df6b7335
...@@ -5,6 +5,87 @@ Model validation reference ...@@ -5,6 +5,87 @@ Model validation reference
As a model both represents data and defines the business rules to which that data must adhere, comprehending data validation is key to using Yii. In order to learn model validation basics, please refer to [Model, Validation subsection](model.md#Validation). As a model both represents data and defines the business rules to which that data must adhere, comprehending data validation is key to using Yii. In order to learn model validation basics, please refer to [Model, Validation subsection](model.md#Validation).
### 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']],
];
}
```
This guide describes all of Yii's validators and their parameters. This guide describes all of Yii's validators and their parameters.
......
...@@ -8,8 +8,56 @@ pass them to [models](structure-models.md), inject model results into [views](st ...@@ -8,8 +8,56 @@ pass them to [models](structure-models.md), inject model results into [views](st
and finally generate outgoing responses. and finally generate outgoing responses.
Controllers are composed by *actions* which are the most basic units that end users can address and request for Controllers are composed by *actions* which are the most basic units that end users can address and request for
execution. A controller can have one or multiple actions. For example, you can have a `post` controller which execution. A controller can have one or multiple actions.
contains a `view` action. End users can request this `view` action which may display a requested post.
The following example shows a `post` controller with two actions: `view` and `create`:
```php
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model !== null) {
return $this->render('view', [
'model' => $model,
]);
} else {
throw new NotFoundHttpException;
}
}
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
}
```
In the `view` action (defined by the `actionView()` method), the code first loads the [model](structure-models.md)
according to the requested model ID; If the model is loaded successfully, it will display it using
a [view](structure-views.md) named `view`. Otherwise, it will throw an exception.
In the `create` action (defined by the `actionCreate()` method), the code is similar. It first tries to populate
the [model](structure-models.md) using the request data and save the model. If both succeed it will redirect
the browser to the `view` action with the ID of the newly created model. Otherwise it will display
the `create` view through which users can provide the needed input.
## Routes <a name="routes"></a> ## Routes <a name="routes"></a>
...@@ -386,64 +434,13 @@ to fulfill the request: ...@@ -386,64 +434,13 @@ to fulfill the request:
## Best Practices <a name="best-practices"></a> ## Best Practices <a name="best-practices"></a>
In a well-designed application, controllers are often very thin with each action containing only a few lines of code. In a well-designed application, controllers are often very thin with each action containing only a few lines of code.
The main role of these code is to invoke appropriate [models](structure-models.md) with the request data If your controller is rather complicated, it usually indicates that you should refactor it and move some code
and use [views](structure-views.md) to present the models. to other classes.
The following code is a typical example showing how the `view` and `create` actions should be implemented
in a controller.
```php
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model !== null) {
return $this->render('view', [
'model' => $model,
]);
} else {
throw new NotFoundHttpException;
}
}
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
}
```
In the `view` action, the code first loads the model according to the requested model ID; If the model
is loaded successfully, it will display it using the view named `view`. Otherwise, it will throw an exception.
In the `create` action, the code is similar. It first tries to populate the model using the request data
and save the model. If both succeed it will redirect the browser to the `view` action with the ID of
the newly created model. Otherwise it will display the `create` view through which users can provide the needed input.
In summary, a controller In summary, controllers
* may access the [request](runtime-requests.md) data; * may access the [request](runtime-requests.md) data;
* may send commands to [models](structure-models.md) and [views](structure-views.md); * may call methods of [models](structure-models.md) and other service components with request data;
* should return the [response](runtime-responses.md) data; * may use [views](structure-views.md) to compose responses;
* should NOT process the request data; * should NOT process the request data - this should be done in [models](structure-models.md);
* should NOT build the response data. * should avoid embedding HTML or other presentational code - this is better done in [views](structure-views.md).
If your controller is rather complicated, it usually indicates that you should refactor it and move some code
to other classes.
...@@ -7,12 +7,12 @@ the [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) ar ...@@ -7,12 +7,12 @@ the [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) ar
You can create model classes by extending [[yii\base\Model]] or its child classes. The base class You can create model classes by extending [[yii\base\Model]] or its child classes. The base class
[[yii\base\Model]] supports many useful features: [[yii\base\Model]] supports many useful features:
* Attributes: represent the business data; * [Attributes](#attributes): represent the business data and can be accessed like normal object properties
* Attribute labels: specify the display labels for attributes; or array elements;
* Massive attribute assignment: supports populating multiple attributes in a single step; * [Attribute labels](#attribute-labels): specify the display labels for attributes;
* Data validation: validates input data based on the declared validation rules; * [Massive assignment](#massive-assignment): supports populating multiple attributes in a single step;
* Data export: allows exporting model data in terms of arrays without customizable formats; * [Validation](#validation): ensures input data based on the declared validation rules;
* Array access: supports accessing model data like an associative array. * [Data Exporting](#data-exporting): allows model data to be exported in terms of arrays with customizable formats.
The `Model` class is also the base class for more advanced models, such as [Active Record](db-active-record.md). 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. Please refer to the relevant documentation for more details about these advanced models.
...@@ -21,7 +21,7 @@ Please refer to the relevant documentation for more details about these advanced ...@@ -21,7 +21,7 @@ Please refer to the relevant documentation for more details about these advanced
components built to support [[yii\base\Model]], it is usually the preferable base model classes. components built to support [[yii\base\Model]], it is usually the preferable base model classes.
## Attributes ## Attributes <a name="attributes"></a>
Attributes are the properties that represent business data. By default, attributes are *non-static public* 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]]. member variables if your model class extends directly from [[yii\base\Model]].
...@@ -67,13 +67,13 @@ foreach ($model as $name => $value) { ...@@ -67,13 +67,13 @@ foreach ($model as $name => $value) {
} }
``` ```
You may override [[yii\base\Model::attributes()]] if you want to support different ways of defining attributes. The method [[yii\base\Model::attributes()]] defines and returns the names of the attributes in a model.
For example, [[yii\db\ActiveRecord]] does so and defines attributes according to table columns. Note that you You may override this method to support different ways of defining attributes. For example, [[yii\db\ActiveRecord]]
may also need to override the magic methods such as `__get()`, `__set()` so that the attributes can be accessed does so by returning table column names as attribute names. Note that you may also need to override the magic
like normal object properties. methods such as `__get()`, `__set()` so that the attributes can be accessed like normal object properties.
## Attribute Labels ## Attribute Labels <a name="attribute-labels"></a>
When displaying values or getting input for attributes, you often need to display some labels associated 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` with attributes. For example, given an attribute named `firstName`, you may want to display a label `First Name`
...@@ -126,11 +126,14 @@ public function attributeLabels() ...@@ -126,11 +126,14 @@ public function attributeLabels()
} }
``` ```
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.
> Info: Strictly speaking, attribute labels are part of [views](structure-views.md). But declaring labels > 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. in models is often very convenient and can result in very clean and reusable code.
## Scenarios ## Scenarios <a name="scenarios"></a>
A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs, 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 but it may also be used for the user registration purpose. In different scenarios, a model may use different
...@@ -138,9 +141,20 @@ business rules and logic. For example, the `email` attribute may be required dur ...@@ -138,9 +141,20 @@ business rules and logic. For example, the `email` attribute may be required dur
but not so during user login. 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. 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`. 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']);
```
To support multiple scenarios, you may override the [[yii\base\Model::scenarios()]] method, like the following: To support multiple scenarios by a single model, you may override the [[yii\base\Model::scenarios()]] method,
like the following:
```php ```php
namespace app\models; namespace app\models;
...@@ -195,358 +209,303 @@ differently based on the current scenario. ...@@ -195,358 +209,303 @@ differently based on the current scenario.
## Validation <a name="validation"></a> ## Validation <a name="validation"></a>
When a model is used to collect user input data via its attributes, it usually needs to validate the affected attributes When the data for a model is received from end users, it should be validated to make sure it satisfies
to make sure they satisfy certain requirements, such as an attribute cannot be empty, an attribute must contain letters certain rules (called *validation rules*, also known as *business rules*). For example, given a `ContactForm` model,
only, etc. If errors are found in validation, they may be presented to the user to help him fix the errors. you may want to make sure all attributes are not empty and the `email` attribute contains a valid email address.
The following example shows how the validation is performed: 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.
You may call [[yii\base\Model::validate()]] to trigger validation. The method will go through every *active rule*
and make sure it is satisfied. If not, an error message will be generated for each failed rule and attribute.
The method returns a boolean value indicating whether all rules are satisfied. If not, you may retrieve the
error messages through the property [[yii\base\Model::errors]]. For example,
```php ```php
$model = new LoginForm(); $model = new \app\models\ContactForm;
$model->username = $_POST['username'];
$model->password = $_POST['password']; // populate model attributes with user inputs
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) { if ($model->validate()) {
// ... login the user ... // all inputs are valid
} else { } else {
$errors = $model->getErrors(); // validation failed: $errors is an array containing error messages
// ... display the errors to the end user ... $errors = $model->errors;
} }
``` ```
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) To declare validation rules associated with a model, override the [[yii\base\Model::rules()]] method by returning
the rules that the model data should satisfy. The following example should the validation rules
If none of the built in validators fit your needs, you can create your own validator by creating a method in you model class. for the `ContactForm` model:
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 ```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() public function rules()
{ {
return [ return [
// ... // the name, email, subject and body attributes are required
[['birthdate'], 'validateAge', 'params' => ['min' => '12']], [['name', 'email', 'subject', 'body'], 'required'],
// the email attribute should be a valid email address
['email', 'email'],
]; ];
} }
``` ```
You may also set other properties of the [[yii\validators\InlineValidator|InlineValidator]] in the rules definition, The `rules()` method returns an array of rules, each of which is an array in the following format:
for example the [[yii\validators\InlineValidator::$skipOnEmpty|skipOnEmpty]] property:
```php ```php
[['birthdate'], 'validateAge', 'params' => ['min' => '12'], 'skipOnEmpty' => false], [
``` // required, specifies which attributes should be validated by this rule.
// For single attribute, you can use the attribute name directly
// without having it in an array instead of an array
['attribute1', 'attribute2', ...],
### Conditional validation // required, specifies the type of this rule.
// It can be a class name, validator alias, or a validation method name
'validator',
To validate attributes only when certain conditions apply, e.g. the validation of // optional, specifies in which scenario(s) this rule should be applied
one field depends on the value of another field you can use [[yii\validators\Validator::when|the `when` property]] // if not given, it means the rule applies to all scenarios
to define such conditions: 'on' => ['scenario1', 'scenario2', ...],
```php // optional, specifies additional configurations for the validator object
['state', 'required', 'when' => function($model) { return $model->country == Country::USA; }], 'property1' => 'value1', 'property2' => 'value2', ...
['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: A rule may be applied to one or multiple attributes. A rule may be applicable only in certain [scenarios](#scenarios).
When a rule is applicable in a scenario, it is called an *active rule* in that scenario.
```php When the `validate()` method is called, it does the following steps to perform validation:
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 1. Determine which attributes should be validated by checking the current [[yii\base\Model::scenario|scenario]]
to add `whenClient`: against the scenarios declared in [[yii\base\Model::scenarios()]]. These attributes are the active attributes.
2. Determine which rules should be applied by checking the current [[yii\base\Model::scenario|scenario]]
against the rules declared in [[yii\base\Model::rules()]]. These rules are the active rules.
3. Use each active rule to validate each active attribute which is associated with the rule.
```php According to the above validation steps, an attribute will be validated if and only if it is
public function rules() an active attribute declared in `scenarios()` and it is associated with one or multiple active rules
{ declared in `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']],
];
}
```
Yii provides a set of built-in validators to support commonly needed data validation tasks. You may also
create your own validators by extending [[yii\validators\Validator]] or writing an inline validation method
within model classes. For more details about the built-in validators and how to create your own validators,
please refer to the [Input Validation](input-validation.md) section.
## Massive Attribute Assignment <a name="massive-assignment"></a> > Note: As a rule of thumb, never trust the data coming from end users and always validate them before
putting them to some good use.
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 ## Massive Assignment <a name="massive-assignment"></a>
$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: 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:
```php ```php
$post = new Post(); $model = new \app\models\ContactForm;
$attributes = [ $model->attributes = \Yii::$app->request->post('ContactForm');
'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 ```php
retrieval that always works for all attributes is that in order to be assigned an attribute should be **safe** else $model = new \app\models\ContactForm;
it will be ignored. $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;
```
### Safe Attributes <a name="safe-attributes"></a>
Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want the attribute to be validated). Massive assignment only applies to the so-called *safe attributes* which are the attributes listed in
We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example: [[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.
```php ```php
['username', 'password', '!secret'] public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
``` ```
In this example `username`, `password` and `secret` are *active* attributes but only `username` and `password` are > Info: The reason that massive assignment only applies to safe attributes is because you want to
considered safe for massive assignment. 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.
Identifying the active model scenario can be done using one of the following approaches: 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.
```php ```php
class EmployeeController extends \yii\web\Controller public function rules()
{ {
public function actionCreate($id = null) return [
{ [['title', 'description'], 'safe'],
// 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.
### Unsafe Attributes <a name="unsafe-attributes"></a>
Validation rules and mass assignment 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
In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation name when declaring it in `scenarios()`, like the `secret` attribute in the following:
rules are described in `rules()` method of the model while what's safe for mass
assignment is described in `scenarios` method:
```php ```php
class User extends ActiveRecord public function scenarios()
{ {
public function rules() return [
{ 'login' => ['username', 'password', '!secret'],
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()`: 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,
```php ```php
$user = User::findOne(42); $model->secret = $secret;
$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 ## Data Exporting <a name="data-exporting"></a>
$user = User::findOne(42);
$user->scenario = 'signup'; Models often need to be exported in different formats. For example, you may want to convert a collection of
$data = [ models into JSON or Excel format. The exporting process can be broken down into two independent steps.
'username' => 'samdark', In the first step, models are converted into arrays; in the second step, the arrays are converted into
'password' => '123', target formats. You may just focus on the first step, because the second step can be achieved by generic
'hashcode' => 'test', data formatters, such as [[yii\web\JsonResponseFormatter]].
];
$user->attributes = $data;
print_r($user->attributes);
```
Will give you the following: The simplest way of converting a model into an array is to use the [[yii\base\Model::attributes]] property.
For example,
```php ```php
array( $post = \app\models\Post::findOne(100);
'username' => 'samdark', $array = $post->attributes;
'first_name' => null,
'password' => '123',
'hashcode' => null, // it's not defined in scenarios method
)
``` ```
In case of not defined `scenarios` method like the following: By default, the [[yii\base\Model::attributes]] property will return the values of *all* attributes
declared in [[yii\base\Model::attributes()]].
```php A more flexible and powerful way of converting a model into an array is to use the [[yii\base\Model::toArray()]]
class User extends ActiveRecord 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.
public function rules() 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).
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 ### Fields <a name="fields"></a>
$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: 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()`.
```php ```php
array( $array = $model->toArray([], ['prettyName', 'fullAddress']);
'username' => 'samdark',
'first_name' => 'Alexander',
'password' => '123',
)
``` ```
If you want some fields to be unsafe for default scenario: 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,
```php ```php
class User extends ActiveRecord // 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()
{ {
function rules() return [
{ // field name is the same as the attribute name
return [ 'id',
['username', 'string', 'length' => [4, 32]],
['first_name', 'string', 'max' => 128],
['password', 'required'],
];
}
public function scenarios() // field name is "email", the corresponding attribute name is "email_address"
{ 'email' => 'email_address',
return [
self::SCENARIO_DEFAULT => ['username', 'first_name', '!password']
];
}
}
```
Mass assignment is still available by default: // field name is "name", its value is defined by a PHP callback
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
```php // filter out some fields, best used when you want to inherit the parent implementation
$user = User::findOne(42); // and blacklist some sensitive fields.
$data = [ public function fields()
'username' => 'samdark', {
'first_name' => 'Alexander', $fields = parent::fields();
'password' => '123',
];
$user->attributes = $data;
print_r($user->attributes);
```
The code above gives you: // remove fields that contain sensitive information
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
```php return $fields;
array( }
'username' => 'samdark',
'first_name' => 'Alexander',
'password' => null, // because of ! before field name in scenarios
)
``` ```
## Data Exporting > 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,
## Best Practices > 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`.
scenarios
validation rules
labels ## 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.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment