CakePHP: Data Validation
Validation is one of the basic and most tedious tasks in application development. If there was ever a reason to use a framework this would be it. There are two ways that appear to be advocated by the CakePHP site and while both have benefits, I'm using a slightly different approach.
Auto-validation
Probably the easiest thing to use is the built-in auto-validation. This is controlled using a $validate
variable inside the model. You can specify requirements for each field using a number of predefined constants. Here's a quick example:
class Profile extends AppModel
{
var $name = 'Profile';
var $validate = array(
'firstname' => VALID_NOT_EMPTY,
'lastname' => VALID_NOT_EMPTY,
'password' => VALID_NOT_EMPTY
);
}
As you can see, I've specified a few fields that must have a value in order to validate. This is great for quick and easy validation but you'll quickly hit the limits of this. Case in point would be requiring a valid email address. You can't specify more than one requirement for a single field. That means that an email could be a required field or a valid email but not both.
The alternative to this is to use custom validation. The wiki seems to recommend overriding the beforeSave()
method of the model and to do your validation manually. Each field that fails can be easily invalidated using the invalidates method of the model. From within the model you can invalidate a field like this:
$this->invalidate('email');
To invalidate from the controller, you can do it like this:
$this->Profile->invalidate('email');
The problem with overriding the beforeSave
is that any validation that fails from the $validate
variable will mean beforeSave
will never fire.
validates()
The alternative is to override the validates method. The validates method in the base model essentially returns true or false as to whether the model validates before saving. So, by overriding this, we can add our custom validation and then return true or false after that. Let's extend the initial example:
class Profile extends AppModel
{
var $name = 'Profile';
var $validate = array(
'firstname' => VALID_NOT_EMPTY,
'lastname' => VALID_NOT_EMPTY,
'password' => VALID_NOT_EMPTY
);
function validates()
{
$profile = $this->data["Profile"];
if($profile["password"] != $profile["password2"]){
$this->invalidate('password2');
}
$errors = $this->invalidFields();
return count($errors) == 0;
}
}
The validates method starts off by loading the profile data in, comparing the two password fields and then invalidating one of them if they don't match (basic password confirmation). Then I count the number of errors and if there are none, the comparison will return true, otherwise it'll return false indicating the model didn't validate.
Note: This is for CakePHP version 1.1.4.3104 . It may be in a full 1.x release but changes still seem to be coming fast and furious with version 1.1.5.3148 already out a week later, version 1.2 just around the corner and version 2.0 coming after that. There's some nice stuff in here but it still feels like beta.
Conversation
Very useful article jonathan. I'm still stumbling around with CakePHP (not having any real projects to use it or the time the play). How about posting this to the wiki?
Good article. Another effective method around an age old problem.
Richard: I wouldn't want to post to the wiki as I don't feel that I have enough ownership or knowledge of the application yet to be contributing directly. On a related note, there are also plans to do away with the wiki.
You could also have a different error tag in your view for every possible validation error, so it can be more verbose:
$this->invalidate('passwordsDontMatch')
But I still think that using the beforeSave callback (along with all the predefined callbacks) is the way to go.
Sosa: the problem with using beforeSave is that you then have to rely on it for ALL of your validation as opposed to offloading some of the basics.
The alternative is to adjust the Cake core to always execute beforeSave, no matter what — which probably isn't a bad idea.
Thanks for the error tag tip. :)
dont forget about beforeValidate() this will allow you to use both the simple method and any custom validation via invalidate(). I wrote a quick example to the google group a few weeks back.
Besides that...great stuff you are putting together on Cake.
Wow. Seems I need to give this a try. Jonathan, my object oriented programming is kind of weak. Should I avoid this at all costs? I like PHP but haven't worked with it at this level. What's your opinion?
As always, an informative article.
I have used beforeValidate() and invalidate() as gwoo suggests
http://groups.google.com/group/cake-php/browse_thread/thread/89acef2ed01dd7ee/e5fe6d771038e51a#e5fe6d771038e51a
EJ: your decision to use Cake should be based on whether you have the time to invest to learn the framework. I'm not sure if a strong knowledge of object-oriented programming is necessary but it certainly helps. It highly depend on the type of application you're putting together.
Sorry, this is a little off topic for this article but…I thought you were a rails guy? Can you give a short talk on why you have chosen to use Cake? I'm very curious as I am thinking about learning it, or Django.
The decision to go with Cake over Rails or Django had a lot to do with the available developer pool. There's a larger pool of PHP developers that can take over the development of the project once it has gone live. The same couldn't be said of ruby or python developers.
Also, I'm a PHP developer and haven't tried ruby or python. Since I'm heading up the development of this project, it was easier to use a framework whose base language I understood over one I didn't.
I would like to try out Rails and Django but it'll have to wait until I have the time to invest in them for personal projects.
Can someone reading this (maybe Jonathan) speak to the display and styling of errors output by the validator?
I built a simple app with validation in the controller for the registration page. How can I get the form to essentially re-render and display errors at each input item when there are validation errors?
p.s.
I ask this question here because I come from a CSS/XHTML background when it comes to code (those are my forte) and I am an Information Architect by study, so I want to display errors with the form in question, not using back or simply displaying them at the top (or bottom) of a form.
Walker: that's probably the easiest thing to do with CakePHP. Basically, for each field that you invalidate, you can use a helper in the view to display the error message. You'll notice how in the example in the article, I invalidated a field called 'password2'. I could display an error message for this field like this:
Yesterday I downloaded cakephp, it seemed to be a nice framework (based on articles here at snook). The only problem I found was that they haven't included a template system. My current framwork is using flexy and it works pretty good. I just hate to write php and html in the same document, to many <?php ?> etc. RoR has liquid, how about cake?
Emil: there really isn't a template system beyond what's part of the view system. However, you are certainly free to use another template system like Smarty along with CakePHP. There's a tutorial on the wiki that explains how to do just that. My personal feeling is that the difference between Smarty and just using some PHP if statements or for loops is extremely minor.
Jonathan: Do you really think its no difference? I'v done 3-4 projects with template systems and I can't go back, the view code is much easier to read, especially when you have large view blocks. From smarty.php.net: "{$title} is less extraneous than <?php echo $title; ?>". Don't you agree with that?
I agree with your example, which is why I use the short form of <?=$title?>. Much more succinct.
@Emil,
One thing you will notice in cake is I have wrote the view class to allow an easy way to extend it. While I have no plans to add a templating system to cake itself, since PHP is a templating language. You are free to extend it and use your own.
I have never used flexy, but I did write a Smarty view class that should give you an start form impementing one for flexy, and I would suggest submitting it as a snippet on the CakeForge site where others my find it useful.
Smarty View class
http://cakeforge.org/snippet/detail.php?type=snippet&id=6
@jonathan,
If you plan to distribute your application I would suggest not using short tags, since some host may turn them off.
Larry, re:short tags: agreed. The app I'm developing won't be for distribution. :)
"RoR has liquid"
Emil, RoR is using eRuby for views, which is ruby embedded in html.
Liquid (which is a ruby port of the django templating) can be used as a plugin, and is basically only used when wanting to do some sort of theming...
Liquid was developed for the use of theming of individual Shopify shops... I dont know of many products/applications actually using liquid...
Ok, I'm sorry - never really tried RoR. Just got amazed over Shopify when it was in beta.
Larry & Jonathan: I'v started develop my own personal homepage/blog and I will do it without any template engine but with cake! I'll be back when I'v tried it for "real".
What I am asking myself is, how does cake handle the difference between an INSERT and an UPDATE? For example: I defined the password has to be filled, but by editing the form I don't want the user to fill in the password, just if he wants to.
Tobi: when using Modelname->save($data), it'll do an INSERT if it doesn't have a model ID and an UPDATE if it does. If you don't want the user to have to enter in the password you can either (A) spit their password back into the form (assuming you're going unencrypted) or (B) simply check if the password field is empty on edit and if so, unset that variable. I believe CakePHP will only update fields that have data in them.
I don't know where you got the idea that you can't declare more than one requirement per field. The following is perfectly valid:
class Profile extends AppModel
{
var $name = 'Profile';
var $validate = array(
'firstname' => VALID_NOT_EMPTY,
'lastname' => VALID_NOT_EMPTY,
'password' => VALID_NOT_EMPTY,
'email' => VALID_NOT_EMPTY,
'email' => VALID_EMAIL
);
}
Though unnecessary, just to demonstrate. You can use Cake's validation for anything, no need for custom functions.
Nick: those issues may be resolved in recent builds but wasn't when this article was posted. That's where I "got the idea".
I have written a custom validates routine that checks for any declared "validateFieldName()" functions and runs them, passing any error messages into an array called (doh!) $errorMessages. After all found functions are called, it checks to see if there's any messages in $errorMessages, and returns false (preventing the save operation) if there are some. If I wanted, I could have called the parent::validates function before returning anything, but I rarely use the $validate array for anything.
@Nick: That's nice if all you need is to check if the field is not empty or is a valid email. I use those constants in my validate functions too. But what about checking if someone is double-registering, of if the two password fields match.
Code igniter has a more flexible validation system. Not that I prefer CI, it's just that.
Cake for everyone!
Thanks mate,
You have saved my feeble brain. It will not be exploding today
:)
Invalidation from the controller did not work in 1.2. Saving the model clears fields in validationErrors array.
I solved this by setting new validation rules that fail with custom messages if I found in controller that data is not valid.
@Marko: likely with the next alpha release of 1.2 you should be able to do custom messages with custom validation. It should be sweet.
I seem to have a problem accessing $this->data[] in the model. Do you know what this issue could be?
Thanks, great work your doing here.
If you don't have anything in $this->data, are you sure the form is set up correctly? It's hard to say but if you haven't already, check out the Google group and post your question there. You'll probably have more success in getting your question answered.
Say if you want to use $validates but not worry about every field in the $validates array being required. Do something along these lines in your model:
function beforeValidate(){
foreach($this->data['ModelName'] as $k => $v){
if(empty($v)){
unset($this->validate[$k]);
}
}
return true;
}
It will only validate when the field is not empty.
Regarding templating vs PHP tags (from June 26, 2006):
I came to PHP from Perl, so it was natural for me to think in terms of heredocs:
While it doesn't deal with iteration or other "complex" coding, it does take care of all those finger-busting tags for simple variable substitution in large HTML blocks.