Validating fields with custom validateField() functions
Posted on 27/1/06 by Felix Geisendörfer
Deprecated post
The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.
Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.
Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).
Update: Tobius has written up a very detailed tutorial concerning custom validation based on the idea presented here. It's definitly worth to have a look at it in the Wiki! But since it's not making use of generateFields() I'll present a solution with generateFields() including error messages here soon.
Yesterday tobius asked me if there was a way of validating the data to be saved in a model in a more individual manner then the current regex way can handle things. So since I didn't know when something like this will be available in CakePHP I offered to write a little function that would support a base for doing it.
All it does is to hook into the beforeSave() function and then looping through all fields passed to the model and then checks a a function named validateField($value) exists in the model and if yes only validates this field if it returns true. Field will alway be CamelBack for the actual field name in the db. so user_name would be validateUserName($value).
In order to use it putt a file called app_model.php inside of your app directory. This is the code to paste in:
{
function beforeSave()
{
foreach ($this->data[$this->name] as $field => $value)
{
$tryToCall = array(&$this, 'validate' . Inflector::camelize($field));
if (is_callable($tryToCall))
{
$result = call_user_func(array(&$this, $tryToCall[1]), $value);
if (!$result)
{
$this->validationErrors[$field] = 1;
}
}
}
if ($this->validationErrors)
return false;
}
}
For anyone who wants to use this: This does not support custom error messages for the validated fields yet. If you need them I can get you on track about what to do for it. Just post in the comments ; ) ...
Associations: I've talked with PhpNut_ about the way associations will work with this and he said the correct way of saving association data is:
$this->ModelB->save($this->params[’data’][’ModelB’]);
and not
So as long as you follow that guidline this should work with just about any model setup. (In case I misunderstood him and this is wrong plz let me know!)
--Felix aka the_undefined
You can skip to the end and add a comment.
uups, yes you are right. I just corrected it.
-> never publish things you haven't double
checked with your IDE ^^.
[...] Felix from presents in his post a nice solution with beforeSave() for doing custom validations. I still have to test it, but it looks very promising. Good job, Felix! [...]
Very nice idea !
Just a little thing : Model::validates() will not be called if Model::beforeSave() returns false. It could be calling Model::validates() first, and then, per field, if no error occured to it, call the field's custom validation. Eg : a member registration form. The generic validation would check if member_id isn't empty (or any other check). If not, then the custom validation will check member_id's availability.
> This does not support custom error messages for the validated fields yet.
Fairly easy to implement. Just return an error string from the callback and instead of setting $this->validationErrors[$field] to 1, set it to the error string. Then modify HtmlHelper::tagErrorMsg in this way :
function tagErrorMsg ($field, $text)
{
$this->setFormTag($field);
$error = $this->tagIsInvalid($this->model, $this->field);
if ( $error !== 0 )
{
return sprintf('%s', is_numeric( $error ) ? empty($text) ? 'Error in field': $text : $error);
}
return null;
}
And you're set : custom error messages from Model's verification !
I like this solution. BTW, check out:
http://groups.google.cl/group/cake-php/browse_thread/thread/745087981eb00a5b
That thread needs a revival :)
In other news...
I tried something like:
$this->ModelA->save($this->params[’data’][’ModelA’]);
$this->ModelB->save($this->params[’data’][’ModelB’]);
...and it did not work. See how it works Model::set() [which is called by save() ] and you will understand.
This will work fine:
$this->ModelA->save(array(’ModelA’=>$this->params[’data’][’ModelA’]));
$this->ModelB->save(array(’ModelB’=>$this->params[’data’][’ModelB’]));
Hey thx JMG and Ricardo for your insightful comments. I'll try out what both of you said myself and then update this post according to it.
And JMG, while your solution would work like a charm, I hate to hack anything inside the cake/ folder so Ill write something that will work without hacking a helper. I think the validateField() function should either give back true if the validation was successful or a string with the error if it wasn't. Then I think I'll overwrite generateFields() in controller and modify the output of it with the custom error messages. Think that will work ...?
Hum, I don't know, I didn't dive enough into Cake's code to say. That's something to try, but maybe a little bit harder to do, even if I agree with you: I should not hack in the cake/ directory. Hopefully I don't do it that much, and I keep a list of modifications needed, so upgrading Cake is just a matter of minutes.
BTW here is the final Model::beforeSave function I use. You'll notice I'm using method_exists() instead of is_callable() which didn't worked as expected: if the callback don't exist is_callable() says it is callable... woups.
function beforeSave()
{
$this->validates();
foreach ( $this->data[$this->name] as $field => $value )
{
if ( empty( $this->validationErrors[$field] ) )
{
$method = 'validate'.Inflector::camelize( $field );
if ( method_exists( $this, $method ) )
{
$result = call_user_func( array( $this, $method ), $value );
if ( $result !== false )
{
$this->validationErrors[$field] = $result;
}
}
}
}
return empty( $this->validationErrors ) ? true : false;
}
FYI, Daniel submitted a patch for the association saving problem:
https://trac.cakephp.org/ticket/330
[...] Improved validation using CakePHP Complex Model Validation Routines Validating fields with custom validateField() functions [...]
This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.
I think it should be:
$this->ModelA->save($this->params['data']['ModelA']);
$this->ModelB->save($this->params['data']['ModelB']);