mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-06 08:14:21 +01:00
440 lines
14 KiB
Plaintext
440 lines
14 KiB
Plaintext
Creating Model
|
|
==============
|
|
|
|
Before writing the HTML code needed by a form, we should decide what kind
|
|
of data we are expecting from end users and what rules these data should
|
|
comply with. A model class can be used to record these information. A
|
|
model, as defined in the [Model](/doc/guide/basics.model) subsection, is
|
|
the central place for keeping user inputs and validating them.
|
|
|
|
Depending on how we make use of the user input, we can create two types of
|
|
model. If the user input is collected, used and then discarded, we would
|
|
create a [form model](/doc/guide/basics.model); if the user input is
|
|
collected and saved into database, we would use an [active
|
|
record](/doc/guide/database.ar) instead. Both types of model share the same
|
|
base class [CModel] which defines the common interface needed by form.
|
|
|
|
> Note: We are mainly using form models in the examples of this section.
|
|
However, the same can also be applied to [active
|
|
record](/doc/guide/database.ar) models.
|
|
|
|
Defining Model Class
|
|
--------------------
|
|
|
|
Below we create a `LoginForm` model class used to collect user input on a
|
|
login page. Because the login information is only used to authenticate the
|
|
user and does not need to be saved, we create `LoginForm` as a form model.
|
|
|
|
~~~
|
|
[php]
|
|
class LoginForm extends CFormModel
|
|
{
|
|
public $username;
|
|
public $password;
|
|
public $rememberMe=false;
|
|
}
|
|
~~~
|
|
|
|
Three attributes are declared in `LoginForm`: `$username`, `$password` and
|
|
`$rememberMe`. They are used to keep the user-entered username and
|
|
password, and the option whether the user wants to remember his login.
|
|
Because `$rememberMe` has a default value `false`, the corresponding option
|
|
when initially displayed in the login form will be unchecked.
|
|
|
|
> Info: Instead of calling these member variables properties, we use the
|
|
name *attributes* to differentiate them from normal properties. An
|
|
attribute is a property that is mainly used to store data coming from user
|
|
input or database.
|
|
|
|
Declaring Validation Rules
|
|
--------------------------
|
|
|
|
Once a user submits his inputs and the model gets populated, we need to
|
|
make sure the inputs are valid before using them. This is done by
|
|
performing validation of the inputs against a set of rules. We specify the
|
|
validation rules in the `rules()` method which should return an array of
|
|
rule configurations.
|
|
|
|
~~~
|
|
[php]
|
|
class LoginForm extends CFormModel
|
|
{
|
|
public $username;
|
|
public $password;
|
|
public $rememberMe=false;
|
|
|
|
public function rules()
|
|
{
|
|
return array(
|
|
array('username, password', 'required'),
|
|
array('rememberMe', 'boolean'),
|
|
array('password', 'authenticate'),
|
|
);
|
|
}
|
|
|
|
public function authenticate($attribute,$params)
|
|
{
|
|
if(!$this->hasErrors()) // we only want to authenticate when no input errors
|
|
{
|
|
$identity=new UserIdentity($this->username,$this->password);
|
|
if($identity->authenticate())
|
|
{
|
|
$duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days
|
|
Yii::app()->user->login($identity,$duration);
|
|
}
|
|
else
|
|
$this->addError('password','Incorrect password.');
|
|
}
|
|
}
|
|
}
|
|
~~~
|
|
|
|
The above code specifies that `username` and `password` are both required,
|
|
`password` should be authenticated, and `rememberMe` should be a boolean.
|
|
|
|
Each rule returned by `rules()` must be of the following format:
|
|
|
|
~~~
|
|
[php]
|
|
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...additional options)
|
|
~~~
|
|
|
|
where `AttributeList` is a string of comma-separated attribute names which
|
|
need to be validated according to the rule; `Validator` specifies what kind of
|
|
validation should be performed; the `on` parameter is optional which specifies
|
|
a list of scenarios where the rule should be applied; and additional options
|
|
are name-value pairs which are used to initialize the corresponding validator's
|
|
property values.
|
|
|
|
There are three ways to specify `Validator` in a validation rule. First,
|
|
`Validator` can be the name of a method in the model class, like
|
|
`authenticate` in the above example. The validator method must be of the
|
|
following signature:
|
|
|
|
~~~
|
|
[php]
|
|
/**
|
|
* @param string the name of the attribute to be validated
|
|
* @param array options specified in the validation rule
|
|
*/
|
|
public function ValidatorName($attribute,$params) { ... }
|
|
~~~
|
|
|
|
Second, `Validator` can be the name of a validator class. When the rule is
|
|
applied, an instance of the validator class will be created to perform the
|
|
actual validation. The additional options in the rule are used to
|
|
initialize the instance's attribute values. A validator class must extend
|
|
from [CValidator].
|
|
|
|
> Note: When specifying rules for an active record model, we can use a
|
|
special option named `on`. The option can be either `'insert'` or
|
|
`'update'` so that the rule is applied only when inserting or updating the
|
|
record, respectively. If not set, the rule would be applied in both cases
|
|
when `save()` is called.
|
|
|
|
Third, `Validator` can be a predefined alias to a validator class. In the
|
|
above example, the name `required` is the alias to [CRequiredValidator]
|
|
which ensures the attribute value being validated is not empty. Below is
|
|
the complete list of predefined validator aliases:
|
|
|
|
- `boolean`: alias of [CBooleanValidator], ensuring the attribute has
|
|
a value that is either [CBooleanValidator::trueValue] or
|
|
[CBooleanValidator::falseValue].
|
|
|
|
- `captcha`: alias of [CCaptchaValidator], ensuring the attribute is
|
|
equal to the verification code displayed in a
|
|
[CAPTCHA](http://en.wikipedia.org/wiki/Captcha).
|
|
|
|
- `compare`: alias of [CCompareValidator], ensuring the attribute is
|
|
equal to another attribute or constant.
|
|
|
|
- `email`: alias of [CEmailValidator], ensuring the attribute is a
|
|
valid email address.
|
|
|
|
- `default`: alias of [CDefaultValueValidator], assigning a default value
|
|
to the specified attributes.
|
|
|
|
- `exist`: alias of [CExistValidator], ensuring the attribute value
|
|
can be found in the specified table column.
|
|
|
|
- `file`: alias of [CFileValidator], ensuring the attribute contains
|
|
the name of an uploaded file.
|
|
|
|
- `filter`: alias of [CFilterValidator], transforming the attribute
|
|
with a filter.
|
|
|
|
- `in`: alias of [CRangeValidator], ensuring the data is among a
|
|
pre-specified list of values.
|
|
|
|
- `length`: alias of [CStringValidator], ensuring the length of the
|
|
data is within certain range.
|
|
|
|
- `match`: alias of [CRegularExpressionValidator], ensuring the data
|
|
matches a regular expression.
|
|
|
|
- `numerical`: alias of [CNumberValidator], ensuring the data is a
|
|
valid number.
|
|
|
|
- `required`: alias of [CRequiredValidator], ensuring the attribute is
|
|
not empty.
|
|
|
|
- `type`: alias of [CTypeValidator], ensuring the attribute is of
|
|
specific data type.
|
|
|
|
- `unique`: alias of [CUniqueValidator], ensuring the data is unique in
|
|
a database table column.
|
|
|
|
- `url`: alias of [CUrlValidator], ensuring the data is a valid URL.
|
|
|
|
Below we list some examples of using the predefined validators:
|
|
|
|
~~~
|
|
[php]
|
|
// username is required
|
|
array('username', 'required'),
|
|
// username must be between 3 and 12 characters
|
|
array('username', 'length', 'min'=>3, 'max'=>12),
|
|
// when in register scenario, password must match password2
|
|
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
|
|
// when in login scenario, password must be authenticated
|
|
array('password', 'authenticate', 'on'=>'login'),
|
|
~~~
|
|
|
|
|
|
Securing Attribute Assignments
|
|
------------------------------
|
|
|
|
After a model instance is created, we often need to populate its
|
|
attributes with the data submitted by end-users. This can be done
|
|
conveniently using the following massive assignment:
|
|
|
|
~~~
|
|
[php]
|
|
$model=new LoginForm;
|
|
if(isset($_POST['LoginForm']))
|
|
$model->attributes=$_POST['LoginForm'];
|
|
~~~
|
|
|
|
The last statement is called *massive assignment* which assigns every entry
|
|
in `$_POST['LoginForm']` to the corresponding model attribute.
|
|
It is equivalent to the following assignments:
|
|
|
|
~~~
|
|
[php]
|
|
foreach($_POST['LoginForm'] as $name=>$value)
|
|
{
|
|
if($name is a safe attribute)
|
|
$model->$name=$value;
|
|
}
|
|
~~~
|
|
|
|
It is crucial to determine which attributes are safe. For example, if we
|
|
expose the primary key of a table to be safe, then an attacker could
|
|
get a chance to modify the primary key of the given record and thus tamper
|
|
the data he is not authorized to.
|
|
|
|
The policy of deciding which attributes are safe is different in version 1.0
|
|
and 1.1. In the following, we will describe them separately.
|
|
|
|
***Safe Attributes in 1.1
|
|
|
|
In version 1.1, an attribute is considered safe if it appears in a validation
|
|
rule that is applicable in the given scenario. For example,
|
|
|
|
~~~
|
|
[php]
|
|
array('username, password', 'required', 'on'=>'login, register'),
|
|
array('email', 'required', 'on'=>'register'),
|
|
~~~
|
|
|
|
In the above, the `username` and `password` attributes are required in `login`
|
|
scenario, while the `username`, `password` and `email` attributes are required
|
|
in `register` scenario. As a result, if we perform a massive assign when in
|
|
`login` scenario, only `username` and `password` will be massively assigned
|
|
since they are the only attributes appearing in the validation rules for `login`.
|
|
On the other hand, if the scenario is `register`, the three attributes can
|
|
all be massively assigned.
|
|
|
|
~~~
|
|
[php]
|
|
// in login scenario
|
|
$model=new User('login');
|
|
if(isset($_POST['User']))
|
|
$model->attributes=$_POST['User'];
|
|
|
|
// in register scenario
|
|
$model=new User('register');
|
|
if(isset($_POST['User']))
|
|
$model->attributes=$_POST['User'];
|
|
~~~
|
|
|
|
So why do we use such a policy to determine if an attribute is safe or not?
|
|
The rationale behind is that if an attribute already has one or several validation rules
|
|
to check its validity, what else should we worry about it?
|
|
|
|
It is important to remember that validation rules are used to check user input data
|
|
rather than the data that we generate in the code (e.g. timestamp, auto-generated primary key).
|
|
Therefore, DO NOT add validation rules for those attributes which do not expect inputs
|
|
from end-users.
|
|
|
|
Sometimes, we want to declare an attribute to be safe, even though we do not really have
|
|
any specific rule for it. An example is an article's content attribute which can take any
|
|
user input. We can use the special `safe` rule to achieve this goal:
|
|
|
|
~~~
|
|
[php]
|
|
array('content', 'safe')
|
|
~~~
|
|
|
|
For completeness, there is also an `unsafe` rule which is used to explicitly declare an
|
|
attribute to be unsafe:
|
|
|
|
~~~
|
|
[php]
|
|
array('permission', 'unsafe')
|
|
~~~
|
|
|
|
The `unsafe` rule is rarely used, and it is an exception to our previous definition of safe
|
|
attributes.
|
|
|
|
|
|
***Safe Attributes in 1.0
|
|
|
|
In version 1.0, the task of deciding whether a data entry is safe or not is based on
|
|
the return value of a method named `safeAttributes` and the specified
|
|
scenario. By default, the method returns all public member variables
|
|
as safe attributes for [CFormModel], while it returns all table columns
|
|
except the primary key as safe attributes for [CActiveRecord]. We may
|
|
override this method to limit safe attributes according to scenarios.
|
|
For example, a user model may contain many attributes, but in `login`
|
|
scenario we only need to use `username` and `password` attributes.
|
|
We can specify this limit as follows:
|
|
|
|
~~~
|
|
[php]
|
|
public function safeAttributes()
|
|
{
|
|
return array(
|
|
parent::safeAttributes(),
|
|
'login' => 'username, password',
|
|
);
|
|
}
|
|
~~~
|
|
|
|
More accurately, the return value of the `safeAttributes` method should be
|
|
of the following structure:
|
|
|
|
~~~
|
|
[php]
|
|
array(
|
|
// these attributes can be massively assigned in any scenario
|
|
// that is not explicitly specified below
|
|
'attr1, attr2, ...',
|
|
*
|
|
// these attributes can be massively assigned only in scenario 1
|
|
'scenario1' => 'attr2, attr3, ...',
|
|
*
|
|
// these attributes can be massively assigned only in scenario 2
|
|
'scenario2' => 'attr1, attr3, ...',
|
|
)
|
|
~~~
|
|
|
|
If the model is not scenario-sensitive (i.e., it is only used
|
|
in one scenario, or all scenarios share the same set of safe attributes),
|
|
the return value can be simplified as a single string:
|
|
|
|
~~~
|
|
[php]
|
|
'attr1, attr2, ...'
|
|
~~~
|
|
|
|
For data entries that are not safe, we need to assign them to the corresponding
|
|
attributes using individual assign statements, like the following:
|
|
|
|
~~~
|
|
[php]
|
|
$model->permission='admin';
|
|
$model->id=1;
|
|
~~~
|
|
|
|
|
|
Triggering Validation
|
|
---------------------
|
|
|
|
Once a model is populated with user-submitted data, we can call [CModel::validate()]
|
|
to trigger the data validation process. The method returns a value
|
|
indicating whether the validation is successful or not. For [CActiveRecord] model,
|
|
validation may also be automatically triggered when we call its [CActiveRecord::save()]
|
|
method.
|
|
|
|
When we call [CModel::validate()], we may specify a scenario parameter.
|
|
Only the validation rules that apply to the specified scenario will be
|
|
executed. A validation rule applies to a scenario if the `on` option
|
|
of the rule is not set or contains the specified scenario name. If we do
|
|
not specify the scenario when calling [CModel::validate()], only those
|
|
rules whose `on` option is not set will be executed.
|
|
|
|
For example, we execute the following statement to perform the validation
|
|
when registering a user:
|
|
|
|
~~~
|
|
[php]
|
|
$model->scenario='register';
|
|
$model->validate();
|
|
~~~
|
|
|
|
> Note: The [scenario|CModel::scenario] property has been available
|
|
> since version 1.0.4. The validation method will take this property
|
|
> value to determine which rules to be checked with. In version 1.0.2
|
|
> and 1.0.3, we need to use the following way to perform scenario-based
|
|
> validation:
|
|
>
|
|
> ~~~
|
|
> [php]
|
|
> $model->validate('register');
|
|
> ~~~
|
|
|
|
We may declare the validation rules in the form model class as follows,
|
|
|
|
~~~
|
|
[php]
|
|
public function rules()
|
|
{
|
|
return array(
|
|
array('username, password', 'required'),
|
|
array('password_repeat', 'required', 'on'=>'register'),
|
|
array('password', 'compare', 'on'=>'register'),
|
|
);
|
|
}
|
|
~~~
|
|
|
|
As a result, the first rule will be applied in all scenarios, while the
|
|
next two rules will only be applied in the `register` scenario.
|
|
|
|
> Note: scenario-based validation has been available since version 1.0.1.
|
|
|
|
|
|
Retrieving Validation Errors
|
|
----------------------------
|
|
|
|
We can use [CModel::hasErrors()] to check if there is any validation
|
|
error, and if yes, we can use [CModel::getErrors()] to obtain the error
|
|
messages. Both methods can be used for all attributes or an individual
|
|
attribute.
|
|
|
|
Attribute Labels
|
|
----------------
|
|
|
|
When designing a form, we often need to display a label for each input
|
|
field. The label tells a user what kind of information he is expected to
|
|
enter into the field. Although we can hardcode a label in a view, it would
|
|
offer more flexibility and convenience if we specify it in the
|
|
corresponding model.
|
|
|
|
By default, [CModel] will simply return the name of an attribute as its
|
|
label. This can be customized by overriding the
|
|
[attributeLabels()|CModel::attributeLabels] method. As we will see in the
|
|
following subsections, specifying labels in the model allows us to create a
|
|
form more quickly and powerful.
|
|
|
|
<div class="revision">$Id$</div> |