Also available in these languages:
DeutschEnglishEspañolFrançaisBahasa Indonesia日本語polskiPortuguêsRomâniaРусскийsvenska简体中文

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 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; if the user input is collected and saved into database, we would use an active record 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 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.

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.

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)
    {
        if(!$this->hasErrors())  // we only want to authenticate when no input errors
        {
            $this->_identity=new UserIdentity($this->username,$this->password);
            if(!$this->_identity->authenticate())
                $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:

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:

/**
 * @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:

Below we list some examples of using the predefined validators:

// 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:

$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:

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,

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.

// 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:

array('content', 'safe')

For completeness, there is also an unsafe rule which is used to explicitly declare an attribute to be unsafe:

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:

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:

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:

'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:

$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 property and therewith indicate which set of validation rules should be applied.

Validation is performed in a scenario basis. The 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:

// 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,

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() method. As we will see in the following subsections, specifying labels in the model allows us to create a form more quickly and powerful.

$Id: form.model.txt 1855 2010-03-04 22:42:32Z qiang.xue $
If you found any typos or errors in the tutorial, please create a Yii ticket to report it. If it is a translation error, please create a Yiidoc ticket, instead. Thank you.

Total 5 comments:

#43
change public attributes to properties didn't work
by hung5s at 1:16pm on January 19, 2009.

I follow the guideline and change: public $username

to

public $name;

public function getusername(){ return $this->name;} public function setusername($value){$this->name = $value; }

The form doesn't work anymore. After posting, $name = NULL and if I change public $name to private name, the $form->Attributes doesn't contain 'name' item.

#44
Do we really need reflection ?
by hung5s at 1:09pm on January 19, 2009.

As far as I can tell, my problem with trying to use CComponent property power to modify form attribute is because CFormModel use reflection to get attributes of the form class and set value to only defined attributes.

A protected attribute uses getter/setter to modify its value does not work in this case.

My question is why do we need to strictly validate form post fields against form class properties ? What happens w/ speed when I want to define 50 properties but have only 2 fields ? I'm not sure PHP refection run with no cost.

#317
LoginForm model must be imported first, and then you can use it in your class.
by revo110 at 5:13am on May 25, 2009.

Hi, I just want to add something, in order to use the LoginForm object, you will need to import it first. Otherwise you will have error, such as:

"Fatal error: Class 'LoginForm' not found in ... "

Now, that error might look simple, but for those who have used MVC Frameworks which were not OOP Strict might be confusing, because "LoginForm" is a model, and as a model it will be already loaded and available without importing it first when you have worked with NOT OOP-Strict MVC Framework.

Sorry, if I am wrong about this info, but I have once used CodeIgniter and while using Yii, I was wondering why would a LoginForm model can not be read in my LoginAction class.

Example to use the import feature provided by Yii: Yii::import('application.modules.authenticate.models.LoginForm');

#338
RE: LoginForm model must be imported first, and then you can use it in your class.
by revo110 at 5:50am on May 30, 2009.

Hi, a revision for the comment above. It was not wrong, but it can be considered as a bad practice.

The better approach would then be to import all models from the application config file.

// autoloading model and component classes
import'=>array(

'application.models.*',

'application.components.*',

),

#629
AR instead
by webscriptz at 9:57pm on September 2, 2009.

it doesn't seem a bad idea to make a note of :" if the user input is collected and saved into database, we would use an active record instead." I needed to read the paragraph 3 times before i saw it (shame)

Your Comment:

You may enter comment using Markdown syntax.

Please login with your forum account.
Note: you must have at least ONE forum post with your account.