Fabien Potencier
10 months ago
Tutorials
Call the expert
16
Jon works on a symfony 1.1 project with a classic login form.
The form is composed of two fields: a username and a password. The validation rules are quite straightforward:
- He wants each field to be required
- He wants to check the correctness of the password
Here is the first implementation of the login form:
class loginForm extends sfForm { public function configure() { $this->setWidgets(array( 'username' => new sfWidgetFormInput(), 'password' => new sfWidgetFormInputPassword(), )); $this->setValidators(array( 'username' => new sfValidatorString(array('required' => true)), 'password' => new sfValidatorString(array('required' => true)), )); $this->widgetSchema->setNameFormat('login[%s]'); } }
This form enforces the requirements on the username and password fields but does not check the correctness of the password.

The password can only be validated if you also have access to the username value. But as you might know, a validator attached to a field does not have access to the other values of the form.
When a validator relies on another submitted value, you need to create a post validator. A post validator is executed after all other validators and is given the whole array of cleaned up values.
To check the password, we will implement a simple callback validator to ensure that the submitted password is equal to the username. Of course, for his real project, Jon will have to call the model layer to do the actual work.
Here is the modified login form with the added post validator:
class loginForm extends sfForm { public function configure() { $this->setWidgets(array( 'username' => new sfWidgetFormInput(), 'password' => new sfWidgetFormInputPassword(), )); $this->setValidators(array( 'username' => new sfValidatorString(array('required' => true)), 'password' => new sfValidatorString(array('required' => true)), )); $this->widgetSchema->setNameFormat('login[%s]'); // add a post validator $this->validatorSchema->setPostValidator( new sfValidatorCallback(array('callback' => array($this, 'checkPassword'))) ); } public function checkPassword($validator, $values) { if ($values['password'] != $values['username']) { // password is not correct, throw an error throw new sfValidatorError($validator, 'Invalid password'); } // password is correct, return the clean values return $values; } }
Now, the form works as expected, but Jon does not like one small thing: if you submit a random password without entering a username, you will have two error messages: a required error for the username and a global error for the wrong password.

But wait a minute, if the username is empty, we don't need to validate the password. Let's change the form to only validate the password if there is a username submitted. This is quite simple, as we just need to ensure that $values['username'] is not empty in the post validator callback:
public function checkPassword($validator, $values) { // before validating the password, check that the username is not empty if (!empty($values['username']) && $values['password'] != $values['username']) { throw new sfValidatorError($validator, 'Invalid password'); } return $values; }
As Jon is quite fussy, he would rather have the 'Invalid password' error message just above the password field instead of having a global error.
That's quite easy to accomplish by throwing an error bound to the password field instead of throwing a global error:
public function checkPassword($validator, $values) { if (!empty($values['username']) && $values['password'] != $values['username']) { $error = new sfValidatorError($validator, 'Invalid password'); // throw an error bound to the password field throw new sfValidatorErrorSchema($validator, array('password' => $error)); } return $values; }

Jon is now happy with his login form and he is ready to take up the next challenge.
Comments 
-
#1 Craig Mason said 41 minutes later

That's a great little tutorial. This demonstrate quickly and efficiently demonstrates the usefulness and flexibility of validators.
-
#2 Jamie said about 1 hour later

Thanks Fabien. Even though I've upgraded to 1.1 I still haven't had a chance to try the new forms (and in fact have been putting it off) .
After reading this and seeing the flexibility I'm going to find some time to make the move.
-
#3 Tonio said about 1 hour later

Nice.
One thing, why are postValidator called if validators failed ?
Because !empty($values['username']) is redundant with first validation rule, or I missed some use case ? -
#4 [MA]Pascal said about 1 hour later

This Jon is pretty good !
-
#5 Nicolas MARTIN said about 2 hours later

Awaiting the 1.1 version of the Askeet tutorial, this kind of recipes can be very helpful to newcomers.
I'm looking forward to read more from the expert ! -
#6 Toc said about 2 hours later

Thank you for your article. It's very useful for trying a new feature of symfony1.1. I think that symfony's documents are cool.
P.S. I translated this article into Japanese in my blog.
-
#7 halfer said about 3 hours later

Great stuff Fabien. I like the way you've done that: my approach conditionally added the post validator based on tainted form inputs, rather than always running the post validator and conditionally throwing the error based on the values supplied (and cleaned?) by symfony.
I'll use this to finish off the My First Project for 1.1, hopefully this weekend, so we can publish it on the 1.1 docs page.
-
#8 Kris Wallsmith said about 3 hours later

This is good. Description of how to remove an error would probably be helpful too (I believe this can be done by passing your error schema to a custom post validator).
-
#9 moreilla said about 13 hours later

that's great. But when will the whole symfony form book come out
-
#10 Yuretsz said 1 day later

Pretty hacky for the framework I wonder.
You ought to make this basic stuff more simpler. -
#11 Guillaume said 1 day later

I totally agree with Tonio. I would enjoy having an additional "only trigger if all field level validators passed" parameter for postValidators, that would be better than rewriting all field validation rules in the post validators.
Thanks anyway for this great framework !! -
#12 Asif Ali Muhammad said 3 days later

Is it a good idea to through error bound to password? I think we are indirectly saying that the username is correct. most of the sites will give a global error like "invalid login" or something like that.
Just curious to know which one is the best method? -
#13 halfer said 3 days later

@Yuretsz - how would *you* make it simpler?
@Asif Ali Muhammad - the error is bound to the password field, but no, this does not say that the username is correct. All it does is acknowledge that the username and password are not empty, which the user (or cracker) knows already.
-
#14 fabien said 3 days later

@Tonio: The post validators are always executed but only with clean values. It means that if a validation failed for whatever reason before the post validator is executed, its entry in the $values array will be empty. I don't see what is redundant as we validate a field based on some other conditions on other fields.
-
#15 Markus.Staab said 3 days later

is there a way to determine inside a post validator if one of the "normal" validators has been considered "failed"?
-
#16 Drfalk3n said 7 days later

great tutorial. useful. great





