Little Symfony Forms Tricks
Par NiKo le jeudi 16 juillet 2009, 23:45 - Dev
- Lien permanent -
9 commentaires -
Tags :
Hey, it’s been a long time I didn’t blog something clever here on Symfony, let me try to remedy this.
I’ve just stumbled upon this blog post about the use of sfValidatorCallback, which is quite cool because by using this particular validator you can virtually employ any kind of php callable to validate something. But as the author warned, it can be problematic to tie the symfony form validation framework to your model classes.
Personnaly, I rather prefer to declare a method in the form class itself to validate some value without boring myself writing each time a new sfValidatorBase derived class[1] :
php
<?php
class myForm extends sfForm
{
public function configure()
{
$this->setWidgets(array(
'name' => new sfWidgetFormInput(),
));
$this->setValidators(array(
'name' => new sfValidatorCallback(array('callback' => array($this, 'validateChuckNorris'))),
));
}
public function validateChuckNorris(sfValidatorBase $validator, $value)
{
// you can't validate chuck norris, but chuck norris can invalidate you
if ('Chuck Norris' === $value)
{
throw new sfValidatorError($validator, 'invalid');
}
}
}
Of course, if you got the exact same need in another form, you should create a dedicated validator class. If you want custom error messages and options, you’ll have to create a dedicated class as well. But for simple and casual needs, this is just enough.
The neat thing with the sfValidatorCallback validator is you can even validate form schema values (say, an array containing all the values bound to the form) the same way, eg. using a post validator. Let’s see an example reusing the form shown previously:
php
<?php
class myForm extends sfForm
{
static protected $choices = array(
'none' => 'No emails',
'commercials' => 'Commercial emails',
'news' => 'Annoucement emails',
'alerts' => 'Alert emails',
);
public function configure()
{
$this->setWidgets(array(
//...
'email' => new sfWidgetFormInput(),
'opt_in' => new sfWidgetFormSelectRadio(array('choices' => self::$choices)),
));
$this->setValidators(array(
//...
'email' => new sfValidatorEmail(array('required' => false)),
'opt_in' => new sfValidatorChoice(array('choices' => array_keys(self::$choices))),
));
$this->validatorSchema->setPostValidator(new sfValidatorCallback(array(
'callback' => array($this, 'validateSchema'),
)));
}
// We want the user to provide his email if he choosed to receive stuff by email
public function validateSchema(sfValidatorBase $validator, array $values)
{
if ($values['opt_in'] !== 'none' && !$values['email'])
{
throw new sfValidatorErrorSchema($validator, array(
'email' => new sfValidatorError($validator, 'required'),
));
}
}
}
As a cool side effect, it will also make testing your form validation very easy, because you just have to test the callable method of your form.
Here’s another trick I stole to Kris on a project we worked together on lately, using a formatter callback to alter the presentation of a widget. Here’s an example showing how to get rid of the unordered list displaying a collection of checkboxes by default, by inlining them instead:
php
<?php
class myForm extends sfForm
{
static protected $choices = array(
'none' => 'No emails',
'commercials' => 'Commercial emails',
'news' => 'Annoucement emails',
'alerts' => 'Alert emails',
);
public function configure()
{
$this->setWidgets(array(
//...
'opt_in' => new sfWidgetFormSelectRadio(array(
'choices' => self::$choices,
'formatter' => array($this, 'formatInline'),
)),
));
$this->setValidators(array(
//...
'opt_in' => new sfValidatorChoice(array('choices' => array_keys(self::$choices))),
));
}
public function formatInline($widget, $inputs)
{
$formatted = array();
foreach ($inputs as $input)
{
$formatted[] = $input['input'].' '.$input['label'];
}
return join(' ', $formatted);
}
}
There are tons of little tricks like these which make the life of a developer using the forms framework easier, I’ll try to share them with you progressively.
Notes
[1] All my examples are using latest Symfony 1.2.
9 commentaires (Ajouter un commentaire)
It's always a pleasure to read you again and again with your ton of tricks... especially for the forms, and also for Doctrine which sometime make me crasy.
Welcome to the South
- ce n'est pas le même que celui de la chanson, mais on pourrait faire toujours en faire une pochette d'album !
Thanks for the shoutout. You reminded me of something I could blog about from that same project. Stay tuned...
LaurentLC> You don't have the need to tell the truth, @ubermuda's trolly nature can't lie.
> Geoffrey said:
> ps: my first comment was sponsored by LaurentLC.
That's a disgusting lie, I was there, I saw you posting this comment on your own initiative
To me, if you need a one-time validation, creating a class, and unit testing it specifically is overkill. Also, when you test your forms, you usually write submission scenarios, so the localized validator *will* be tested anyway.
<privateJoke target="#sensiolabs">You know, I may have read a bit much the "Manual", especially the "Pragmatism" definition... I'm kind of "hugonist" regarding that</privateJoke>Well, my main point was about the testing part since re-usability can still be achieved through refactoring as you say.
But *unit* testing means testing *one* thing at a time, so yes I'm going to test my forms, but I'm going to test them in an other test suite than the validation.
Also, validators can be used in a totally different context than forms, so I really think it makes sense to decouple it entirely from the forms.
My 2 cents
ps: my first comment was sponsored by LaurentLC.
connard> Are you saying you will never test your form object in your project?
Also, wanting absolutely to reuse things even if you don't have the need to is not my cup of tea. I had a lot of time a specific need for a given form, and creating a new validator class just because it's cool and can be useful in the future would have been overkill. You can always refactor your code along the life of the project if you encounter such reuse case anyway.
> As a cool side effect, it will also make testing your form validation very easy
I must respectfuly disagree here, since having to test the callable of your form to test validation leads to instantiating an otherwise non-needed class in your test: the form.
Also, as you state in your post, using sfValidatorCallback does not promote reuse while writting a validator does not take much more time than using sfValidatorCallback.
Anyway, it's nice that you have time to post again
connard