geoB
geoB

Reputation: 4704

Validate form fieldA based on either fieldA or fieldB in Symfony 2

I'm a Symfony noob trying unsuccessfully to visualize how best to validate a form field based on either it or a different field. The case: a form will solicit either a date of birth or an age. If the dob is entered, age is ignored. If age is entered and dob is empty, the dob is said to be today's date less age in years. If neither is entered a validation error is thrown. I've accomplished this with Smarty validation; as a learning exercise I'm trying to reproduce the application in Symfony.

I've looked at this solution where both fields are properties of an entity. In my case age is not, only dob. So it's not clear to me how to apply that solution. I'd greatly appreciate pointers.

Thanks.

George PS: (edited: dreck removed) PPS: (edited: removed to make room for nearly working version) Form:

// src\Mana\AdminBundle\Resources\views\Form\Type\NewClientType.php
namespace Mana\AdminBundle\Form\Type;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Mana\AdminBundle\Validator\Constraints;

class NewClientType extends AbstractType {

    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array('validation_groups' => 'client_new', 
            'validation_constraint' => new DOBorAge(),
            ));
     }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('fname', null, array('required' => false));
        $builder->add('sname', null, array('required' => false));
        $builder->add('dob', 'birthday', array('widget' => 'single_text', 'required' => false));
        $builder->add('age', null, array('mapped' => false, 'required' => false));
    }

    public function getName() {
        return 'client_new';
    }
}

services:

services:
  client_new:
    class: Mana\AdminBundle\Validator\Constraints\DOBorAgeValidator
    scope: request
    tags:
      - { name: validator.constraint_validator, alias: dobage_validator}

Validators:

// src\Mana\AdminBundle\Form\Type\DOBorAge.php
namespace Mana\AdminBundle\Form\Type;
use Mana\AdminBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

class DOBorAge extends Constraint {

    public $message = 'Either a date of birth or age must be present';

    public function validatedBy() {
        return 'dobage_validator';
    }

    public function getTargets() {
        return Constraint::CLASS_CONSTRAINT;
    }

}

and

// src\Mana\AdminBundle\Validator\Constraints\DOBorAgeValidator.php
namespace Mana\AdminBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class DOBorAgeValidator extends ConstraintValidator {

    protected $request;

    public function __construct(Request $request) {
        $this->request = $request;
    }

    public function validate($value, Constraint $constraint) {
        var_dump($this->request->request->get('client'));
        die();
        return true;
    }
}

Upvotes: 1

Views: 2914

Answers (2)

Ahmed Siouani
Ahmed Siouani

Reputation: 13891

I think that the best way to manage this kind of validation is to add a customized validation constraint to your form.

Just add a class that extend Symfony\Component\Validator\Constraint and override the validatedBy() method. You can then specify a customized validator that you've to create so that it contains all the logic of your form validation.

This customized validator should extend Symfony\Component\Validator\ConstraintValidator class and implement the isValid() method for Symfony 2.0.x or validate() method for Symfony 2.1.x.

You can then Check your values, compare them and do whatever you want inside this method and add eventually add violations if one or many of your validation rules aren't respected.

Also check Symfony2 validation based on two fields.

Upvotes: 1

geoB
geoB

Reputation: 4704

A Symfony-esque solution: a single form field that takes either a date or an age, then transform the entry into a date. (Now, if I could only turn this into a custom field...)

namespace Mana\AdminBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Mana\AdminBundle\Form\DataTransformer\AgeToDOB;

class NewClientType extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, array $options) {

        $transformer = new AgeToDOB();

        $builder->add('fname', null, array('required' => false, 
            'invalid_message' => 'First name not be blank',));
        $builder->add('sname', null, array('required' => false,
            'invalid_message' => 'Last name not be blank',));
        $builder->add(
                $builder->create('dob', 'text', array('required' => false,
            ))->addModelTransformer($transformer));
    }

    public function getName() {
        return 'client_new';
    }
}

and the transformer:

namespace Mana\AdminBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Mana\AdminBundle\Entity\Client;

class AgeToDOB implements DataTransformerInterface {

    public function reverseTransform($dob) {

        if (null == $dob) {
            return '';
        }
        if ((substr_count($dob, '/') == 2 && strtotime($dob))) {
            $date = new \DateTime($dob);
            return date_format($date, 'Y-m-d');
        }
        if (is_numeric($dob)) {
            $date = new \DateTime();
            $interval = 'P' . $dob . 'Y';
            $date->sub(new \DateInterval($interval));
            return date_format($date, 'Y-m-d');
        }
    }

    public function transform($client) {
        if (null == $client) {
            return '';
        }
        if (is_object($client)) {
            $dob = $client->getDob();
            // date: test for two / characters
            if ((substr_count($dob, '/') == 2 && strtotime($dob))) {
                $date = new \DateTime($dob);
                return date_format($date, 'm/d/Y');
            }
            if (is_numeric($dob)) {
                $date = new \DateTime();
                $interval = 'P' . $dob . 'Y';
                $date->sub(new \DateInterval($interval));
                return date_format($date, 'm/d/Y');
            }
        }
    }

}

Upvotes: 1

Related Questions