As default we have single var $validate rule set in the model class which is triggered on each save() method call while saving form data. This is quite logical and sufficient for single form fields to validate but what if we have another form with somewhat different fields which we want to post and validate? As an answer i know of three different approaches.
Approach One
As per the latest release (not sure about previous releases) cakephp treats model validations “if and only if” way. In other words, a field will only be validated if it is present in the $this->data data set and not flaged as ‘require’=>true in the var $validate rule set. This approach may come handy when there is small number of forms (1-2) and also small number of fields for each form.
I will try to explain it using examples. Let us assume we have a registration process broken down in two steps. Below is the step one form view.
echo $form->create("RegisterStep1",array('controller'=>'Users','action'=>'Register1')); echo $form->input('User.firstname'); echo $form->input('User.lastname'); echo $form->input('User.email'); echo $form->input('User.password'); echo $form->input('User.password_confirm'); echo $form->submit(' Next Step '); echo $form->end();
And step two is:
echo $form->create("RegisterStep2",array('controller'=>'Users','action'=>'Register2')); echo $form->input('User.city'); echo $form->input('User.state'); echo $form->select('User.country',$countries); echo $form->input('User.phone'); echo $form->submit('Finish Registration'); echo $form->end();
here is the model validation rule set:
var $validate = array( 'firstname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Firstname must contain letters and spaces only.'), 'lastname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Lastname must contain letters and spaces only.'), 'email' => array('rule' => VALID_EMAIL, 'message' => 'Please enter a valid email address.') 'password' => array('rule' => array('minLenght', 6), 'message' => 'Password must be at least 6 characters long.'), 'password_confirm' => array('rule' => 'confirmPassword', 'message' => 'Passwords do not match.'), 'city' => array('rule' => '/[A-Za-z ]+/', 'message' => 'City name must contain letters and spaces only.'), 'state' => array('rule' => '/[A-Za-z ]+/', 'message' => 'State name must contain letters and spaces only.'), 'country' => array('rule' => VALID_NOT_EMPTY, 'message' => 'You must select a country.'), 'phone' => array('rule' => 'phone', 'message' => 'Please enter a phone number.'), );
and finally controller functions. Registration Step 1 submits data to Register1 function which looks like this :
function Register1() { //other logic if(!empty($this->data)) { $this->User->save($this->data); } //other logic }
and Register2 like this one.
function Register2() { //other logic if(!empty($this->data)) { $this->User->save($this->data); } //other logic }
A simple and straight forward approach! But, someone with a habit of keeping code neat and clean may not like this way as it would mix up the validation rules for all forms and also it may become quite messed up with the increasing number of forms and/or form fields. So we need to start thinking about a different way which would provide a somewhat independent and separate validation rules. I myself adopted this approach (two) until i came across the third one (explained later).
Approach Two
In one of my cakephp projects i had to break a large form into two different forms and then to save them to a single model. My form views remained same as exampled above but what i did, i created a dummy model “Register” with $useTable=”users”. Now i had two models one User and other Register with the following sampled lines of code respectively.
class User extends AppModel{ var $name="User"; var $validate = array( 'firstname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Firstname must contain letters and spaces only.'), 'lastname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Lastname must contain letters and spaces only.'), 'email' => array('rule' => VALID_EMAIL, 'message' => 'Please enter a valid email address.') 'password' => array('rule' => array('minLenght', 6), 'message' => 'Password must be at least 6 characters long.'), 'password_confirm' => array('rule' => 'confirmPassword', 'message' => 'Passwords do not match.') ); //custom functions like confirmPassword etc. here }
and
class Register extends AppModel{ var $useTable="users"; var $validate = array( 'city' => array('rule' => '/[A-Za-z ]+/', 'message' => 'City name must contain letters and spaces only.'), 'state' => array('rule' => '/[A-Za-z ]+/', 'message' => 'State name must contain letters and spaces only.'), 'country' => array('rule' => VALID_NOT_EMPTY, 'message' => 'You must select a country.'), 'phone' => array('rule' => 'phone', 'message' => 'Please enter a phone number.') ); }
Hold on! As I get form data in $this->data[‘User’] format i need to hack data inside my controller’s Register2 function so i could submit that data to $this->Register model object.
Here is the alteration in Register2:
function Register2() { //other logic if(!empty($this->data)) { $this->data['Register'] = $this->data['User'];//passing User data to Register if($this->Register->save($this->data)) { //logic } else { $this->data['User'] = $this->data['Register'];//need to pass Register data back to User so we can show errors etc. } } //other logic }
And it worked. I know it was a cheap hack but i didn’t have much time to think about the third one (behaviour thing) and my forms were so big that i could not use the first one, it worked just fine for me.
Approach Three
Use of MultivalidatableBehavior by
Class UserModel extends AppModel { var $name = 'User'; var $actsAs = array('Multivalidatable'); /** * Default validation rule set */ var $validate = array( 'fieldname' => array('rule' => 'rulehere', 'message' => 'message here.') //more validation rules here ); /** * Custom validation rules sets */ var $validationSets = array( 'register1' => array( 'firstname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Firstname must contain letters and spaces only.'), 'lastname' => array('rule' => '/[A-Za-z ]+/', 'message' => 'Lastname must contain letters and spaces only.'), 'email' => array('rule' => VALID_EMAIL, 'message' => 'Please enter a valid email address.') 'password' => array('rule' => array('minLenght', 6), 'message' => 'Password must be at least 6 characters long.'), 'password_confirm' => array('rule' => 'confirmPassword', 'message' => 'Passwords do not match.') ), 'register2' => array( 'city' => array('rule' => '/[A-Za-z ]+/', 'message' => 'City name must contain letters and spaces only.'), 'state' => array('rule' => '/[A-Za-z ]+/', 'message' => 'State name must contain letters and spaces only.'), 'country' => array('rule' => VALID_NOT_EMPTY, 'message' => 'You must select a country.'), 'phone' => array('rule' => 'phone', 'message' => 'Please enter a phone number.') ) ); //custom function like confirmPassword etc. here }
function Register1() { //other logic if(!empty($this->data)) { $this->User->setValidation('register1');//calling specific validation only $this->User->save($this->data); } //other logic }
and Register2:
function Register2() { //other logic if(!empty($this->data)) { $this->User->setValidation('register2');//calling specific validation only $this->User->save($this->data); } //other logic }
Isn’t a more precise and clean approach? Furthermore, the method setValidation() also accepts as parameter an array with the ruleset:
$this->User->setValidation(array('email' => array('rule' => 'email', 'message' => 'Must be a valid email address')));
And finally here is the source code of behaviour class
<?php class MultivalidatableBehavior extends ModelBehavior { /** * Stores previous validation ruleset * * @var Array */ var $__oldRules = array(); /** * Stores Model default validation ruleset * * @var unknown_type */ var $__defaultRules = array(); function setUp(&$model, $config = array()) { $this->__defaultRules[$model->name] = $model->validate; } /** * Installs a new validation ruleset * * If $rules is an array, it will be set as current validation ruleset, * otherwise it will look into Model::validationSets[$rules] for the ruleset to install * * @param Object $model * @param Mixed $rules */ function setValidation(&$model, $rules = array()) { if (is_array($rules)){ $this->_setValidation($model, $rules); } elseif (isset($model->validationSets[$rules])) { $this->setValidation($model, $model->validationSets[$rules]); } } /** * Restores previous validation ruleset * * @param Object $model */ function restoreValidation(&$model) { $model->validate = $this->__oldRules[$model->name]; } /** * Restores default validation ruleset * * @param Object $model */ function restoreDefaultValidation(&$model) { $model->validate = $this->__defaultRules[$model->name]; } /** * Sets a new validation ruleset, saving the previous * * @param Object $model * @param Array $rules */ function _setValidation(&$model, $rules) { $this->__oldRules[$model->name] = $model->validate; $model->validate = $rules; } } ?>
And thats all for multiple validation rule sets for a single model! You certainly are very patient to be with me its being a long post! Thank You.
very nice and helpful
THanks
P.s.
right solution:
‘rule’ => array(‘custom’, ‘/^[A-Za-z ]+$/’),
I know this is a response on an old post but…
You know this rule isn’t a good solution?
‘firstname’ => array(‘rule’ => ‘/[A-Za-z ]+/’, ‘message’ => ‘Firstname must contain letters and spaces only.’),
It wont allow input like ‘84165’ but it will allow this: ‘jelle842’.
Right Jelle. Anyways it is just an example not a solution. Thanks for your input.
Good idea!