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(),
                ),
            ))
        );
    }
}
murilolobato
  • 349
  • 4
  • 14

1 Answers1

18

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();
        }
    }
}
Jan Rydrych
  • 2,188
  • 2
  • 13
  • 18
  • 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. – murilolobato Mar 22 '17 at 23:45
  • `$form = $context->getRoot()` is exactly what I needed! Thanks! – StockBreak Jan 23 '19 at 16:46
  • 1
    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`. – Will B. Feb 06 '20 at 18:02