7

I'm creating a custom FormType called IntervalType. My IntervalType will have two fields, start and end and will be of type integer. This custom FormType will always be used without data_class.

I want to add a constraint to guarantee that start is lower than end.

How do I use the Symfony\Component\Validator\Constraints\Callback directly in a FormType without data_class?

Here is my IntervalType, just for reference:

// src/AppBundle/Form/Type/IntervalType.php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Validator\Constraints\NotBlank;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('start', IntegerType::class, array(
                'constraints' => array(
                    new NotBlank(),
                ),
            ))
            ->add('end', IntegerType::class, array(
                'constraints' => array(
                    new NotBlank(),
                ),
            ))
        );
    }
}

1 Answer 1

20

When the form won't be using any data_class the only option seems to be the Callback constraint.

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

class IntervalType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('start', IntegerType::class, array(
                'constraints' => array(
                    new NotBlank(),
                ),
            ))
            ->add('end', IntegerType::class, array(
                'constraints' => array(
                    new NotBlank(),
                    new Callback(array($this, 'validateInterval')),
                ),
            ))
            ->add('submit', SubmitType::class);
    }

    public function validateInterval($value, ExecutionContextInterface $context)
    {
        $form = $context->getRoot();
        $data = $form->getData();

        if ($data['start'] >= $value) {
            $context
                ->buildViolation('The end value has to be higher than the start value')
                ->addViolation();
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

It works parcially. When I embedded the IntervalType inside another form, it fails, because $context->getRoot() returns the root form, so $data['start'] doesn't exists.
$form = $context->getRoot() is exactly what I needed! Thanks!
You can alternatively use $form = $context->getObject()->getParent(); where $context->getObject() will be the FormInterface of the field the Callback assertion is assigned. This is useful when using an embedded form collection, to return the FormInterface object that contains the fields, instead of the top level (root) FormInterface.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.