phpsymfonyvalidation

CollectionType validation groups not working


I try to validate FormType inside CollectionType with some simple groups rules but It doesn't work, but if i try to make the same without validations groups, it's work fine. Any idea?

This is a complete and simple exemple that reproduct the error https://github.com/ychakroun/symfony-collection-type-issue

    /**
    * Sticker
    *
    * @ORM\Table(name="sticker")
    * @ORM\Entity(repositoryClass="App\Repository\StickerRepository")
    */
    class Sticker 
    {
       /**
         * @var \Doctrine\Common\Collections\Collection
         *
         * @ORM\OneToMany(targetEntity="App\Entity\Link", mappedBy="sticker", cascade={"persist", "remove"}, orphanRemoval=true)
         * @ORM\OrderBy({"position"="ASC"})
         * @Assert\Valid()
         */
        private $links;
    }
/**
 * Link
 *
 * @ORM\Table(name="link")
 * @ORM\Entity(repositoryClass="App\Repository\LinkRepository")
 */
    class Link
    {

    /**
     * @var mixed
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string|null
     * @Assert\NotBlank()
     *
     * @ORM\Column(name="title", type="string")
     */
    private $title;

    /**
     * @var bool
     *
     * @ORM\Column(name="external", type="boolean")
     */
    private $external;

    /**
     *
     * @var string|null
     *
     * @Assert\NotBlank(groups={"isExternal"})
     * @Assert\Url(groups={"isExternal"})
     * @ORM\Column(name="url", type="text", nullable=true)
     */
    private $url;

    /**
     * @var \App\Entity\PageVersion|null
     *
     * @Assert\NotBlank(groups={"isInternal"})
     * @ORM\ManyToOne(targetEntity="App\Entity\PageVersion")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="page_version_id", referencedColumnName="id", nullable=true)
     * })
     */
    private $pageVersion;

    /**
     * @var \App\Entity\Category|null
     *
     * @Assert\NotBlank(groups={"isInternal"})
     * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="links")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=true)
     * })
     */
    private $category;

    /**
     * @var \App\Entity\Sticker|null
     *
     * @ORM\ManyToOne(targetEntity="App\Entity\Sticker", inversedBy="links")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="sticker_id", referencedColumnName="id", nullable=true)
     * })
     */
    private $sticker;
}

And this is the forms i use:

class StickerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('links', CollectionType::class, [
                'entry_type' => LinkType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'delete_empty' => true,
                'attr' => [
                    'class' => 'collection',
                ],
                'by_reference' => false,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Sticker::class,
        ]);
    }
}
class LinkType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class, [
                'label' => 'Titre du menu:',
                'attr' => [
                    'input-group' => 'true',
                ],
            ])
            ->add('external', ChoiceType::class, [
                'label' => false,
                'expanded' => true,
                'choices' => [
                    'Lien interne' => false,
                    'Lien externe' => true,
                ],
                'choice_attr' => [
                    'class' => 'link-type',
                ],
                'label_attr' => [
                    'class' => 'btn-group btn-group-toggle',
                    'data-toggle' => 'buttons',
                ]
            ])
            ->add('url', UrlType::class, [
                'label' => 'SAISISSEZ L\'URL EXTERNE',
                'attr' => ['placeholder' => 'https://'],
            ])
            ->add('pageVersion', EntityType::class, [
                'required' => false,
                'class' => Page::class,
                'choice_label' => 'name',
            ])
            ->add('category', EntityType::class, [
                'required' => false,
                'class' => Category::class,
                'choice_label' => 'title',
                'query_builder' => function (CategoryRepository $er) {
                    return $er->createQueryBuilder('c')->where('c.enabled = 1');
                },
            ])
            ->add('position', HiddenType::class, [
                'attr' => [
                    'class' => 'my-position',
                ],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Link::class,
            'cascade_validation' => true,
            'validation_groups' => function (FormInterface $form) {
                /** @var Link $link */
                $link = $form->getData();
                $groups = ['Default'];
                if ($link->getExternal()) {
                    $groups[] = 'isExternal';
                } else {
                    $groups[] = 'isInternal';
                }

                return $groups;
            },
        ]);
    }
}

We can see that the url field is validated and it's blank

We can see that the url field is validated and it's blank

If i try to remove groups={"isExternal"} from link entity, the validation will work, like in this image: enter image description here


Solution

  • This option is only valid on the root form and is used to specify which groups will be used by the validator.

    This is the response https://github.com/symfony/symfony/issues/31441