mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-04 07:14:06 +01:00
381 lines
13 KiB
Plaintext
381 lines
13 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;
|
|
|
|
private $_identity;
|
|
|
|
public function rules()
|
|
{
|
|
return array(
|
|
array('username, password', 'required'),
|
|
array('rememberMe', 'boolean'),
|
|
array('password', 'authenticate'),
|
|
);
|
|
}
|
|
|
|
public function authenticate($attribute,$params)
|
|
{
|
|
$this->_identity=new UserIdentity($this->username,$this->password);
|
|
if(!$this->_identity->authenticate())
|
|
$this->addError('password','Incorrect username or 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].
|
|
|
|
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.
|
|
|
|
- `date`: alias of [CDateValidator], ensuring the attribute represents
|
|
a valid date, time, or datetime value.
|
|
|
|
- `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.
|
|
|
|
|
|
###Declaring Safe Attributes
|
|
|
|
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.
|
|
|
|
|
|
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] models,
|
|
validation may also be automatically triggered when we call its [CActiveRecord::save()]
|
|
method.
|
|
|
|
We can set a scenario with the [scenario|CModel::scenario] property and
|
|
therewith indicate which set of validation rules should be applied.
|
|
|
|
|
|
Validation is performed in a scenario basis. The [scenario|CModel::scenario]
|
|
property specifies which scenario the model is being used in and which set
|
|
of validation rules should be used. For example, in the `login` scenario, we only want to
|
|
validate the `username` and `password` inputs of a user model; while in the `register`
|
|
scenario, we need to validate more inputs, such as `email`, `address`, etc.
|
|
The following example shows how to perform validation in the `register` scenario:
|
|
|
|
~~~
|
|
[php]
|
|
// creates a User model in register scenario. It is equivalent to:
|
|
// $model=new User;
|
|
// $model->scenario='register';
|
|
$model=new User('register');
|
|
|
|
// populates the input values into the model
|
|
$model->attributes=$_POST['User'];
|
|
|
|
// performs the validation
|
|
if($model->validate()) // if the inputs are valid
|
|
...
|
|
else
|
|
...
|
|
~~~
|
|
|
|
The applicable scenarios that a rule is associated can be specified via
|
|
the `on` option in the rule. If the `on` option is not set, it means the
|
|
rule will be used for all scenarios. For example,
|
|
|
|
~~~
|
|
[php]
|
|
public function rules()
|
|
{
|
|
return array(
|
|
array('username, password', 'required'),
|
|
array('password_repeat', 'required', 'on'=>'register'),
|
|
array('password', 'compare', 'on'=>'register'),
|
|
);
|
|
}
|
|
~~~
|
|
|
|
The first rule will be applied in all scenarios, while the
|
|
next two rules will only be applied in the `register` scenario.
|
|
|
|
|
|
Retrieving Validation Errors
|
|
----------------------------
|
|
|
|
Once validation is done, any possible errors will be stored in the model
|
|
object. We can retrieve the error messages by calling [CModel::getErrors()]
|
|
and [CModel::getError()]. The difference between the two methods is that
|
|
the first method will return *all* errors for the specified model attribute
|
|
while the second method will only return the *first* error.
|
|
|
|
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> |