Advanced validation
This tutorial is based on a post I wrote in my blog.
Preparation
Before we can use the advanced validation approach presented in this tutorial, we have to do some preparations. Add the function “invalidFields()” to your /app/app_model.php. If you do not have such a file, you can copy the file /cake/app_model.php and put it in the /app/ directory.
Please notice that there exist two different versions of the “invalidFields()” function: use the first one if you use CakePHP with a version up to 1.0.1.2708, otherwise use the second “invalidFields()” function.
function invalidFields ($data = array()) { if (!isset($this->validate) || !empty($this->validationErrors)) { if (!isset($this->validate)) { return true; } else { return $this->validationErrors; } } if ($data == null) { if (isset($this->data)) { $data = $this->data; } else { $data = array(); } } $errors = array(); $this->set($data); foreach ($data as $table => $field) { foreach ($this->validate as $field_name => $validators) { foreach($validators as $validator) { if (isset($validator[0])) { if (method_exists($this, $validator[0])) { if (isset($data[$table][$field_name]) && !call_user_func(array(&$this, $validator[0]))) { if (!isset($errors[$field_name])) { $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; } } } else { if (isset($data[$table][$field_name]) && !preg_match($validator[0], $data[$table][$field_name])) { if (!isset($errors[$field_name])) { $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; } } } } } } } $this->validationErrors = $errors; return $errors; }
Use this “invalidFields” function if you are using a CakePHP version higher than 1.0.1.2708:
function invalidFields ($data = array()) { if(!$this->beforeValidate()) { return false; } if (!isset($this->validate) || !empty($this->validationErrors)) { if (!isset($this->validate)) { return true; } else { return $this->validationErrors; } } if (isset($this->data)) { $data = array_merge($data, $this->data); } $errors = array(); $this->set($data); foreach ($data as $table => $field) { foreach ($this->validate as $field_name => $validators) { foreach($validators as $validator) { if (isset($validator[0])) { if (method_exists($this, $validator[0])) { if (isset($data[$table][$field_name]) && !call_user_func(array(&$this, $validator[0]))) { if (!isset($errors[$field_name])) { $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; } } } else { if (isset($data[$table][$field_name]) && !preg_match($validator[0], $data[$table][$field_name])) { if (!isset($errors[$field_name])) { $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; } } } } } } } $this->validationErrors = $errors; return $errors; }
The next, optional, step you have to do is to create the file /app/views/helpers/error.php with the following content. You do not have to do this step if you intend to print the error messages with HtmlHelper::tagErrorMsg().
class ErrorHelper extends Helper { function showMessage($target) { list($model, $field) = explode('/', $target); if (isset($this->validationErrors[$model][$field])) { return sprintf('<div class="error_message">%s</div>', $this->validationErrors[$model][$field]); } else { return null; } } }
Usage
So, with the preparations done, we are ready to use the advanced validation approach. Let us validate the username field of a user. Our username must not be empty and it must be unique. For the first validation rule, we can use the predefined CakePHP constant VALID_NOT_EMPTY. For the second validation rule, we write a validation function called “isUsernameUnique”. This function must return true if it validates, otherwise false.
class User extends AppModel { var $validate = array('username' => array(array(VALID_NOT_EMPTY, 'Username is required'), array('isUsernameUnique', 'Username not unique'))); function isUsernameUnique() { if ( $this->id == null ) //Adding a user { return (!$this->hasAny( array( 'User.name' => $this->data[$this->name]['name'] ) )); } else //Editing a user { return (!$this->hasAny( array( 'User.name' => $this->data[$this->name]['name'], 'User.id' => '!='.$this->data[$this->name]['id'] ) ) ); } } }
The validation logic is defined, so we can go to our view and print out the error messages. We do it with:
echo $error->showMessage('User/username');
As we use the ErrorHelper to print the error messages, we have to add the ErrorHelper to the $helpers array in our controller.
var $helpers = array('Html', 'Error');
That’s it :)
If, for some reason, you should need to disable a specific validation condition on a field, a simple way to do it is to put the following into your controller:
$this->User->validate['username']['required'] = false; //Now username is no longer required, but must still be unique
Usage (with HtmlHelper::tagErrorMsg()
You also can use the HtmlHelper::tagErrorMsg() function to print the error messages. The usage is almost identical, you simply omit the error messages.
class User extends AppModel { var $validate = array('username' => array(array(VALID_NOT_EMPTY), array('isUsernameUnique'))); function isUsernameUnique() { return (!$this->hasAny(array('User.username' => $this->data[$this->name]['username']))); } }
And then you do in your view as usual:
echo $html->tagErrorMsg('User/username', 'Your message');
Turning off validation conditions works the same as above.
Variations Of This Validation
Advanced Validation With Parameters
Link: Advanced Validation with Parameters
Description: Allows one to create one validation method and pass different parameters to it.
Reason: Allows for better reuse of valdation methods.
Improved Advanced Validation With Parameters
Link: Improved Advanced Validation with Parameters
Description: Provides some improvement on Advanced Validation with Parameters by removing the need for calling a function to load validation data. Also, uniformizes validation type (as constants) and removes the need to specify parameters which can be automatically inferred.
Reason: Validation should be transparent on the controller’s side.