- Specifying a scenario
- Creating scenario specific validation rules
- CActiveRecord scenarios
- Conclusion
Scenarios are an extremely useful tool for separating validation tasks on any class you use derived from CModel. In this tutorial we will use CActiveRecord.
Specifying a scenario ¶
The first thing is to initialize the Model instance with a scenario. This can be done one of two ways.
1. New CActiveRecord instance with constructor parameter ¶
$model = new MyActiveRecord('formSubmit');
In this example MyActiveRecord extends CActiveRecord and formSubmit is a validation scenario.
2. Pre-existing CActiveRecord instance from find() or other scource ¶
$model = MyActiveRecord::model()->find('id = :id', array(':id' => 1);
$model->scenario = 'formSubmit';
This example is the same as example one except we are switching to a scenario on a pre-existing Model instance.
Creating scenario specific validation rules ¶
Firstly it is important to note that any rules not assigned a scenario will be applied to all scenarios. This can be useful for building a pseudo-inheritance structure of rules. Beware however that setting a validator for any property on a model marks it as safe for massive assignments unless the 'unsafe' rule is used.
Further Reading:
Reference: Model rules validation
Understanding "Safe" Validation Rules
Example of scenario rules: ¶
public function rules() {
return array(
array('name', 'required'),
array(
'name', 'match', 'not' => true, 'pattern' => '/[^a-zA-Z0-9 ]/',
'message' => 'Name must consist of letters, numbers and spaces only', 'on' => 'formSubmit'
),
);
}
Note that the rule property that specifies a scenario is 'on' so in this example if there is no scenario then the only rule that applies to name is required. If the Model is set to the scenario 'formSubmit' then the required rule will still apply but so will the match rule.
So to break this down:
$model = new MyActiveRecord('formSubmit');
$model->name = 'Joe Blogs';
if ($model->validate()) {
//this will pass validation as both the required rule and the match rule are satisfied
}
$model = new MyActiveRecord('formSubmit');
$model->name = 'Joe Blogs - is awesome!!!!';
if ($model->validate()) {
//this will fail validation as the match rule is not satisfied
}
$model = new MyActiveRecord();
$model->name = 'Joe Blogs - is awesome!!!!';
if ($model->validate()) {
//this will pass validation as the match rule is no longer checked
}
$model = new MyActiveRecord('formSubmit');
$model->name = null;
if ($model->validate()) {
//this will fail validation as the required rule is not satisfied
}
//Note: the default scenario for CActiveRecord is 'insert' so keep this in mind
//when using these examples
CActiveRecord scenarios ¶
CActiveRecord sets a number of scenarios internally, any rules you set are validated against these scenarios depending on what operation is performed.
1. Insert ¶
All instances of CActiveRecord instantiated via the constructor with the new keyword are set to this scenario by default. Any rules with the scenario will only be validated on database insert (first call to the save() method).
2. Update ¶
All instances of CActiveRecord instantiated via the find() methods are set to this scenario by default. Any rules with the scenario will only be validated on database updates (subsequent calls to the save() method).
3. Search ¶
This is not used internally but it is worth a mention as all the Gii generated model and crud code uses the 'search' scenario. The models generated by Gii create a rule like:
array('id, email, last_ip, created, last_login', 'safe', 'on' => 'search'),
This allows the mass assignment of these attributes when the scenario is set to search.
Conclusion ¶
Now when you validate user inputs you can stratify or separate different input rules on validation. An example of this is wanting to apply more rigid rules to users then to say internal business logic.
best practice
I think it is best to do
$model = new ModelName('scenario');
of course if possible in the specific case you have...
@dckurushin
Yeah, you're probably right, I just wanted to point out that it is possible to change a scenario once a model is already initialized.
Tip on Dynamic Scenario's
I had to come up with a solution where site admins could change required fields on an input form. so i did the following:
Instead of returning an array of the static rules I set them to a variable.
public function rules() { // NOTE: you should only define rules for those attributes that // will receive user inputs. $rules = array( array('invoice_id, payment_type_id', 'numerical', 'integerOnly'=>true), array('line_1, line_2, line_3, line_4, line_5, line_6, line_7, line_8, line_9, line_10', 'length', 'max'=>100), // The following rule is used by search(). // Please remove those attributes that should not be searched. array('id, invoice_id, payment_type_id, line_1, line_2, line_3, line_4, line_5, line_6, line_7, line_8, line_9, line_10', 'safe', 'on'=>'search'), array('invoice_id', 'required'), ); $rules = array_merge($rules, $this->findDynamicRules()); return $rules; }
This function is called from rule() which looks at the current Scenario and retrieves and builds the necessary rules for that situation. In this case its payment types, and waht lines are required.
private function findDynamicRules(){ $hold = array(); $fil = InvoiceBodyPaymentTypePattern::model()->findAll('payment_type_id = :payID', array(':payID'=>$this->scenario)); $x = 0; $str = ""; foreach ($fil as $row) { if ($row->required_yn == 1){ if($x == 0){ $str = 'line_'.$row->line_id; $x++; }else{ $str .= ', line_'.$row->line_id; } } if($row->filtered_yn == 1){ array_push($hold, array('line_'.$row->line_id, $row->filter_name)); } } if($x != 0){ array_push($hold, array($str,'required')); } return $hold; }
@tom@cu
If you use dynamic rules be aware that validatorList is created only once, so any changes later on that might affect the rules may not have any effect. So, to get around that we must implement a way to recreate the validatorList, which doesn't seem like a good design due to its performance. I wonder if there's a better way that we can implement a scheme of real dynamic validation rules.
@Sepia
It is possible to add a validator to the validatorList:
$validator = new CNumberValidator; $validator->attributes = array('id'); $validator->integerOnly = true; $model->validatorList->add($validator);
Should function the same as:
array('id', 'numerical', 'integerOnly' => true),
in the rules array.
@Luke Jurgs
That way we still have to detect for changes and update it. The list must be able to change when the result of findDynamicRules() changes.
Default scenarios ?
What about a default scenario list set by Yii on new, search, etc.. ?
@realtebo
Good point, thanks, I'll update with a section dealing with the scenarios used by the CModel derivative classes.
scenario
hi friend for suing the validation on particular action we used scenario here is best example to you
public function rules()
{ // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( array('email,role_id,password,create_time', 'required'), array('full_name', 'length', 'max'=>30, 'min' => 3,'message' =>'Incorrect username (length between 3 and 30 characters).), array('full_name', 'match' ,'pattern'=>'/^[A-Za-z_]+$/u', 'message'=>'Username can contain only alphanumeric characters and hyphens(-).'), array('status_id, role_id, 'numerical', 'integerOnly'=>true), array('phone_number,device_id', 'length','min'=>10, 'max'=>20), array('password', 'length','min'=>6, 'max'=>512), array('phone_number', 'match', 'pattern'=>'/^[0-9-()\s+]+$/'), array('email','email'), array('create_time', 'length', 'max'=>64), array('email_id', 'required','on' => 'update'), array('phone_number,password', 'required','on' => 'create'), array('password_2', 'required','on' => 'create,api'), array('old_password,password_2', 'required','on' => 'changepassword'), array('password_2', 'compare', 'compareAttribute'=>'password','on' => 'create,api,changepassword'),
// The following rule is used by search(). // Please remove those attributes that should not be searched. array('id, email, password, phone_number, activation_key, status_id, role_id, create_time', 'safe', 'on'=>'search'), ); }
this rule apply in model
and when you go on action like create
you need to write
$model = new user('create');//here create scenarioa applied.
use it and enjoy..
'on' and 'except' are arrays.
Nice tutorial. I think it's worth mentioning that 'on' parameter is an array, so when defining rules 'on' can be a comma separated list of scenarios. Another very important parameter which is available since 1.1.11 is 'except'. A great tool for blacklisting unwanted scenarios.
create or insert???
maybe scenario 'on'=>'create' doesn't work but when replace with 'on'=>'insert' it's works
The scenario string cannot contain spaces
I dont know why but it seems that you cannot have spaces in your scenario string.
This works:
array('my_attribute', 'required', 'on' => 'my_scenario'),
CRequiredValidator will be present in the validators array
$model = new ModelName(); $model->setScenario('my_scenario'); $model->getValidators('my_attribute'); // will return the CRequiredValidator
But when we have a space in our scenario string, the validator is not present:
array('my_attribute', 'required', 'on' => 'My Scenario'),
CRequiredValidator will NOT be present in the validators array
$model = new ModelName(); $model->setScenario('My Scenario'); $model->getValidators('my_attribute'); // no CRequiredValidator
I just observed this behavior right now. I did not tracked the cause yet.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.