The data validation in the current version of CakePHP is limited, so different people presented alternatives (solution of tobius and Felix Geisendörfer, solution of Myles Eftos). Well, I was not fully happy with these solutions, and so I have written my own data validation thing ;-) Nonetheless, a big THANK YOU goes to the aforementioned people, as their work has influenced my solution. And I hope that my solution inspires someone to write an even better solution!

Ok, let us dive in the code. First the model:

class User extends AppModel
{
    var $validate = array('username' => array(
                         array(VALID_NOT_EMPTY,
                                 'Username is required'),
			 array('isUsernameUnique',
                                 'Not unique')));

    function isUsernameUnique()
    {
        return (!$this->hasAny(array('User.username' =>
                  $this->data[$this->name]['username'])));
    }
}

In our AppModel (in app/app_model.php) we have to override the function “invalidFields()”. Please note that I present two slightly different “invalidFields()” functions. You need the first version if you use CakePHP up to version 1.0.1.2708, otherwise you have to 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 version of “invalidFields()” if you are using a CakePHP with a version number 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;
}

As “HtmlHelper::tagErrorMsg()” does not fit our needs, we have to write our own function, which we put in a custom helper:

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;
        }
    }
}

In your view you can simply use (don’t forget to add the error helper to the helper array in your controller):

echo $error->showMessage('User/username');

That’s it :)

Update (2006-02-12): I created from this post a short tutorial.

Update (2006-04-04): Fixed a small bug in the function invalidFields. The two lines

$this->validationErrors = $errors;
return $errors;

should be outside of the foreach loop. Thanks to Chris for the hint.

Update (2006-05-06): Fixed a small bug which occured with version 1.0 of CakePHP. I changed the default value for the parameter of the invalidFields() function and replaced the first line of that function:

if (!isset($this->validate) || is_array($this->validationErrors))

with

if (!isset($this->validate) || !empty($this->validationErrors))

Thanks to Zachary Naiman!

Update (2006-05-09): Added a code block to invalidFields() which is necessary if you are using a CakePHP revision higher than 2708.

Update (2006-05-11): Created a second version of the function “invalidFields()” which should be used when using CakePHP with a version number higher than 1.0.1.2708.