Dynamic Validation Error Messages in CakePhp
Problem
In CakePhp, I want to invalidate a field submitted in a form and set the error message dynamically. If the error message is set by a validation parameter within the model, the new message should override that error message and be displayed by the form in the view.
Overview
Consider the following three cases as a developer:
1. You have a textarea field which requires user input of at least 12 words. When the user inputs less than 12 words, you’d like to display a message that states, “This field requires a response of at least 12 words. Your response was $x words long” where $x is the actual number of words in the user’s input.
2. You have a form with three input fields. None of them is required in and of itself. However, for the form to be valid, 2 of the 3 must be filled in.
3. You have an application in which one user submits a form which is then reviewed and approved by a second. The form has a “summary” field which the first user may fill in but does not have to. However, the second user must fill it in if it is empty.
Each of these requirements describes a somewhat complex validation case that CakePhp’s normal method for configuring data validation within the model doesn’t adequately address.
Solution
Rather than using the static form of validation described in the documentation, or even a custom validation rule which, although it may suffice, is still oriented around a single field with a static method, I recommend validating the field dynamically from within the controller or a custom model method using the model’s invalidate field.
The following commented code demonstrates a technique which could be used to satisfy each of the three cases above:
<?php
class MyModel extends AppModel
{
var $name = 'MyModel';
var $my_textarea_min_word_req = 12;
var $ValidCodeList = array(
0 => 'pass',
1 => 'fail',
2 => 'requires further review'
);
// snipped
// essentially a wrapper for save with some extra circumstantial validation
function update_record($FormData)
{
$UpdateData = $FormData['MyModel'];
// situation-specific validation: this code is only required in this
// instance so we don't set a rule within the model's validation parameters
// above. note: this could also be given its own separate method
$textarea_wordlen = str_word_count($UpdateData['my_textarea']);
if ( $textarea_wordlen < $this->my_textarea_min_word_req )
{
$s_ = ( $textarea_wordlen === 1 ) ? '' : 's'; // for the grammar nazis
// note that it is here we're dynamically setting the error message
$this->invalidate('my_textarea',
"This field requires at least {$this->my_textarea_min_word_req} words. Your response was {$textarea_wordlen} word{$s_}.");
}
// in update only, an internal code is required
if ( !isset($this->ValidCodeList[$UpdateData['internal_code']]) )
$this->invalidate('internal_code', 'please select a valid review code');
// if the other fields are valid, the model will be saved
if ( $this->validates($UpdateData) )
{
$this->create();
$this->id = $UpdateData['id']; // set id to update record
return $this->save($UpdateData);
}
// return false (which will lead back to the form)
else
{
return 0;
}
}
}
?>
In the end, it’s pretty simple and straightforward. Still, I could not find it clearly spelled out in any one place in the CakePhp documentation or elsewhere on the web.
One caveat to note that will further explain the inclination to forego usage of the model’s validate parameter above the I came across here: http://groups.google.com/group/cake-php/msg/095804b352539b97?hl=en. If you do have a validation rule set for that field within the model, the message will get overridden by the default error message there if the field also fails the validation set there.