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.

 
tutorials/advanced_validation.txt · Last modified: 2006/09/14 01:11 by dho