0
votes

Symfony2.8 project Subform validation by annotation NOT YML No entities or doctrine involvement

Main form validates by annotation OK. Collection field, subform fails to validate. Every field in the subform is passed as valid when the main form fields are valid no matter what you input.

I have tried lots of suggestions online,

'error_bubbling', 'cascade_validation', and the @Valid() tags. Nothing works.

I've added var_dump(...) to RangeValidator class in the symfony2 codebase and it is not created/invoked. To prove its not used, I copied a subform field to the main form and it then calls the framework's validator for range checking which it should do for some fields in the subform, only its not.

Obviously there is more code, including client side javascript but here is a basic code fragment below (hashed up from my code for this question):

Main form:

class SubsetAdd extends AbstractType
{
    /**
     * @var string
     *
     * @Assert\NotBlank(
     *        message="Subset name should not be blank"
     * )
     * @Assert\Regex(
     *        pattern="/^[\w\s\d\:\_\-\+]{1,64}$/",
     *        message="Subset name should contain ..."
     * )
     */
    protected $subsetName;


    /**
     * @var string
     *
     * @Assert\Regex(
     *        pattern="/^[\d]{1,4}[Kk]{1}$/",
     *        message="Baud rate should be <n>K"
     * )
     */
    protected $baud;

    /**
     * @Assert\Valid()
     */
    protected $entries;


    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->setMethod('POST');

        $builder->add('subsetName',
            Type\TextType::class,
            array(
                'attr' => array(
                    'title' => 'Subset name string',
                ),
                'required' => true,
                'label' => 'Subset'
            )
        );
        $builder->add('baud', Type\ChoiceType::class, array(
            'label' => 'Baud',
            'attr' => array(
                'title' => 'Baud rate',
                ),
            'placeholder' => 'Select Baud',
            'required' => true,
            'choices' => array('9600'=>'9600','115200'=>'115200'),
            ));

        $builder->add('entries', Type\CollectionType::class, array(
            'label' => 'Entries',
            'type' => Module\EntryType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'required' => true,
            'prototype' => true,
            'prototype_name' => 'entries__name__',
        ));

        if ($options['submit']) {
            // manual debugging aid.  normally client side ajax does the submit so no button required
            $builder->add('submit',Type\SubmitType::class);
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired(array(
            'submit',
        ));

        $resolver->setDefaults(array(
            'submit'=>false,
            'translation_domain' => 'SerialBundle-forms',
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => __CLASS__,        // this class is the data object the form represents
        );

        return $options;
    }

    public function getName()
    {
        $cn=substr(__CLASS__,(strlen(__NAMESPACE__)>0?1+strlen(__NAMESPACE__):0));    // class name - namespace

        return strtolower(substr($cn,0,1)).substr($cn,1);    // lower case first letter
    }

    public function getBlockPrefix() {
        return $this->getName();
    }

    public function setSubsetName($name)
    {
        $this->subsetName = $name;
        return $this;
    }

    public function getSubsetName()
    {
        return $this->subsetName;
    }

    public function setBaud($baud)
    {
        $this->baud = $baud;
        return $this;
    }

    public function getBaud()
    {
        return $this->baud;
    }

    public function setEntries($entries) {
        $this->entries=$entries;
    }
    public function getEntries() {
        return $this->entries;
    }
}


Subform:

class EntryType extends AbstractType
{
    /**
     * @var string
     *
     * @Assert\Regex(
     *        pattern="/^[\dA-F]+$/",
     *        message="Ident should be hex value"
     * )
     * @Assert\Valid()
     */
    protected $ident;

    /**
     * @var integer
     *
     * @Assert\Range(
     *      min = 0,
     *      max = 64,
     *        minMessage="Start minimum value?",
     *        maxMessage="Start maximum value?"
     * )
     * @Assert\Valid()
     */
    protected $start;

    /**
     * @var integer
     *
     * @Assert\Range(
     *      min = 0,
     *      max = 64,
     *        minMessage="Length minimum value?",
     *        maxMessage="Lenght maximum value?"
     * )
     * @Assert\Valid()
     */
    protected $length;


    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->setMethod('POST');

        $builder->add('ident',
            Type\TextType::class,
            array(
                'attr' => array(
                    'title' => 'Ident value',
                ),
                'required' => true,
                'label' => 'Ident'
            )
        );

        $builder->add('start',
            Type\TextType::class,
            array(
                'attr' => array(
                    'title' => 'Start value',
                ),
                'required' => true,
                'label' => 'Start'
            )
        );

        $builder->add('length',
            Type\TextType::class,
            array(
                'attr' => array(
                    'title' => 'Length value',
                ),
                'required' => true,
                'label' => 'Length'
            )
        );
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired(array(
        ));

        $resolver->setDefaults(array(
            'translation_domain' => 'SerialBundle-forms',
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => __CLASS__,        // this class is the data object the form represents
        );

        return $options;
    }

    public function getName()
    {
        $cn=substr(__CLASS__,(strlen(__NAMESPACE__)>0?1+strlen(__NAMESPACE__):0));    // class name - namespace

        return strtolower(substr($cn,0,1)).substr($cn,1);    // lower case first letter
    }

    public function getBlockPrefix() {
        return $this->getName();
    }

    public function setIdent($id)
    {
        $this->ident = $id;

        return $this;
    }

    public function getIdent()
    {
        return $this->ident;
    }

    public function setStart($start)
    {
        $this->start = $start;

        return $this;
    }

    public function getStart()
    {
        return $this->start;
    }

    public function setLength($length)
    {
        $this->length = $length;

        return $this;
    }

    public function getLength()
    {
        return $this->length;
    }
}


Basic controller:

class SubsetsController extends Controller
{
    ...

    /**
     * @Route("/add", name="subsets-add")
     * @Method({"GET","POST"})
     */
    public function addFormAction(Request $request)
    {
        $subsetAdd=new ModuleForm\SubsetAdd();

        $options=array(
            'submit'=>false,
        );
        $form = $this->createForm(ModuleForm\SubsetAdd::class, $subsetAdd, $options);

        $form->handleRequest($request);

        if ($form->isSubmitted()) {        
            var_dump('Submitted');
            if ($form->isValid()) {
                var_dump('Valid');

                var_dump("<pre>");
                var_dump($subsetAdd->getEntries());
                var_dump("</pre>");
            }
        }

        return $this->render("SerialBundle:Subsets/form.subsets.html.twig', array(
            'anyerrors' => (($form->isSubmitted() && !$form->isValid())?true:false),
            'form' => $form->createView(),    // invoked after handlerequest
        ));
    }
}
1

1 Answers

1
votes

What about using 'constraints' => new Valid() ?

 $builder->add('entries', Type\CollectionType::class, array(
        'constraints' => new Valid(),
        'label' => 'Entries',
        'type' => Module\EntryType::class,
        'allow_add' => true,
        'allow_delete' => true,
        'required' => true,
        'prototype' => true,
        'prototype_name' => 'entries__name__',
  ));