Conditional validator in Symfony, another approach

November 5th, 2008 by Carlos Barros

Today morning I was reading the Symfony blog, more specifically A week of symfony #96 and then I came across a tutorial talking about conditional validators.This is very usefull, I find myself using this technique very often, so I decided to write about another techinique I use sometimes to implement a conditional validation, but for a different scenario. Imagine the following: you have a contact form where the users specify how they want to be contacted, email or phone, throught a select box. Then you have two input boxes where the users can enter both email and phone. If the user opts to be contacted via email, email field becomes mandatory and phone number optional, and the opposite happens if the user opts to be contacted via phone. So, how to implement such a form in symfony?


A normal form would look like this:

lib/form/ContactForm.class.php:

class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'method'	=> new sfWidgetFormSelect(array('choices'=>array('phone'=>'Phone','email'=>'Email'))),
      'email'	=> new sfWidgetFormInput(),
      'phone'	=> new sfWidgetFormInput(),
      'msg'	=> new sfWidgetFormTextarea(),
    ));
 
    $this->setValidators(array(
      'method'	=> new sfValidatorChoice(array(
			'choices'=>array('phone','email'),
		)),
      'email'	=> new sfValidatorEmail(array(
			'required'=>true,
		),array(
			'required'=>'Please enter your email address',
			'invalid'=>'Invalid email address'
		)),
      'phone'	=> new sfValidatorString(array(
			'required'=>true,
		),array(
			'required'=>'Please enter your phone number',
		)),
      'msg'	=> new sfValidatorString(array(
			'required'=>true,
		),array(
			'required'=>'Please enter your message',
		)),
    ));
 
    $this->widgetSchema->setLabels(array(
	'email'		=> 'Email:',
	'phone'		=> 'Phone:',
	'method'	=> 'Contact type:',
	'msg'		=> 'Message:',
    ));
 
    $this->widgetSchema->setNameFormat('contact[%s]');
    $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
  }

The problem with this form is the fact that both phone and email are ALWAYS mandatory, regardless of the value of method. So, what we need is a way to intercept method’s value and update validators. So, what we need is intercept the bind method in order to check method’s value:

lib/form/ContactForm.class.php:

  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
    if(@$taintedValues["method"] == 'phone') $this->validatorSchema["email"]->setOption('required',false);
    else $this->validatorSchema["phone"]->setOption('required',false);
 
    return parent::bind($taintedValues,$taintedFiles);
  }

As can be seen, we create our own bind method, overriding parent one, and before calling the actual bind method, we inspect the values sent by the user and if method is phone, we remove the required option from email validator, otherwise we remove the required option from phone validator and finally call parent method.

The resulting form can be seen here!

Share this post...
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Ma.gnolia
  • MySpace
  • Rec6
  • Reddit
  • StumbleUpon
  • Technorati

Comments

      DanielNo Gravatar in
    • That was great, thank you.

      As you are so firm with the new form framework and nobody even cared to answer my question in the forum, I’ll give it a try here.

      The question is here: http://www.symfony-project.org/forum/index.php/t/16625/

      I have widgets in many forms that have the same default options (years array in sfWidgetFormDate for example) and the form framework generates a base form that all base forms in the base/ directory inherit from. In the forms book there’s an example on how to set the default form formatter name for all these forms. But how can I preconfigure a widget in this class? It really is annoying to have this non-DRY boiler-plate code in all forms. Maybe you can help me.

      Thanks for this awesome post! Cheers, Daniel

      Éric RogéNo Gravatar in
    • Great, I had the same issue, and until now, the only way I’ve founded was to extend the doClean method with my own validatorSchema.

      My only critic with your way is that the form won’t work if you embed it in anthor form.

      PS: I think that instead in your bind method, you should use $this->validatorSchema->offsetUnset(‘phone’);
      It will remove all the validators on the field, and not only the required one.

      Carlos BarrosNo Gravatar in
    • Hi Daniel, I just replied to you thread, I hope this works for you.

      Eric, thanks for your comments. Yes, it won’t work if you embed this form into another one, but it’s simple to make it work. For instance, if you embed this form into an "options" field of another form, you can just move the bind method into the parent form and change it a bit:

        public function bind(array $taintedValues = null, array $taintedFiles = null)
        {
          if(@$taintedValues["options"]["method"] == 'phone') $this->validatorSchema["options"]["email"]->setOption('required',false);
          else $this->validatorSchema["options"]["phone"]->setOption('required',false);
       
          return parent::bind($taintedValues,$taintedFiles);
        }

      About you idea about un-setting the entire validator works fine too, but I didn’t to this because even though the field is not required, we still accept it and we should still validate it. In my example, if the user choose to be contacted by phone, but he says in the comments that if I couldn’t reach him by phone it’s fine to send an email, we still need to validate email field against sfValidatorEmail to ensure it’s a valid email address. But in case your action will simply "discard" the value, your idea works fine.

      Thanks

      Carlos BarrosNo Gravatar in
    • Hi Frederic,

      Unfortunately I still didn’t install Symfony 1.2 here so I don’t have experience with this helper. But looking at the code, I’m afraid this helper won’t do what u want. My suggestion is to create the link by your own, using something like this:

      <a href="<?=url_for('author/list') ? rel="nofollow">?filters[author_id]=<?=$author->getId()?>&filter=filter">articles</a>

      And then enable author_id (I’m assuming author_id is you column name) filter in the article view. I didn’t use link_to here because it does not allows to add URI parameters (well, I read that Symfony 1.2 allows this, but didn’t know how it works). Also, this link works with Symfony 1.1 admin generator, not sure it will work for 1.2 version.

      Hope this helps

      RickyNo Gravatar in
    • Nice snippet, found it really useful.

      Ended up modifying it a bit to suit my needs, but your original concept helped a lot!

      Cheers

      Marget BieryNo Gravatar in
    • Hey web entrepreneurs ! You have great ideas and are looking for the best value coding company and/or looking for investors to help you fund your project? Just enter this place, I found it days ago, it seems to be in alpha but the concept seems really good ! <a href="http://www.codingate.com" rel="nofollow">CODINGATE</A> .

      free nintendo pointsNo Gravatar in
    • Amazing! Thank you! I always desired to produce in my site something like that. Should i quote portion of your post to my weblog?

      diets to loose weightNo Gravatar in
    • Ultimately, an issue that I am fervent about. I have looked for information of this topic for the last several hours. Your site is greatly treasured.

Leave a Reply

Powered by WP Hashcash