在 Symfony 3 中为集合的每个元素应用特定的验证组

Posted

技术标签:

【中文标题】在 Symfony 3 中为集合的每个元素应用特定的验证组【英文标题】:Apply specific validation group for each element of a collection in Symfony 3 【发布时间】:2018-08-15 16:10:46 【问题描述】:

我不得不将我的一个项目从 symfony 2.8 升级到 symfony 3.4,我注意到验证过程发生了巨大变化。

为了简化,假设我有一个 User 实体,其中包含许多 Addresses 实体。 当我创建/更新我的用户时,我希望能够添加/删除/更新任意数量的地址。所以在symfony 2.8中我遇到了这种情况

用户

我使用注释验证器

src/AppBundle/Entity/User.php

//...
class User

    //...
    /** 
     * @Assert\Count(min=1, max=10)
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Address", mappedBy="user", cascade="persist", "remove")
     */
    protected $addresses;
    //...

用户表单

src/AppBundle/Form/UserForm.php

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

    $builder
        // ...
        ->add('addresses', CollectionType::class, [
            'type' => AddressType::class,
            'cascade_validation' => true,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
        ])
    ;


public function setDefaultOptions(OptionsResolverInterface $resolver)

    $resolver->setDefaults([
        'data_class' => User::class,
        'cascade_validation' => true,
        'validation_groups' => // User's logic
    ]);

地址

src/AppBundle/Entity/Address.php

//...
class Address

    //...
    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="user")
     */
    protected $user;

    /**
     * @Assert\NotBlank(groups="zipRequired")
     * @ORM\Column(type="text", nullable="true")
     */
    protected $zipCode;
    //...

地址表格

src/AppBundle/Form/AddressForm.php

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

    $builder
        // ...
        ->add('zipCode', TextType::class)
    ;


public function setDefaultOptions(OptionsResolverInterface $resolver)

    $resolver->setDefaults([
        'data_class' => Address::class,
        'cascade_validation' => true,
        'validation_groups' => function(FormInterface $form) 
            /** @var Address $data */
            $data = $form->getData();
            $validation_groups = [];

            // Simplified here, it's a service call with heavy logic
            if ($data->doesRequireZip()) 
                $validation_groups[] = 'zipRequired';
            

            return $validation_groups;
        ,
    ]);

在 symfony 2.8 中

在添加的 3 个地址中,两个必须有效 zipRequired 组,一个不有效。我工作!

在 symfony 3.4 中

我在 User::$zipCode 声明中添加了 @Assert\Valid() 并删除了 'cascade_validation' => true(不在方法 configureOptions 但它似乎未使用),因为它已被弃用。

但现在添加了 3 个地址,其中两个必须验证 zipRequired 组,一个不能:只使用用户的类验证器组,所以我可以验证包含不连贯数据的表单

我检查了 xdebug 并调用了 AddressForm 中的 validator_groups 回调,但没有调用验证器。

我测试了这里描述的解决方案:Specify different validation groups for each item of a collection in Symfony 2?,但它不能再工作了,因为在 symfony 3.4 中 cascade_validation 在属性上抛出错误

在我的情况下,所涉及的逻辑过于繁重,无法使用此处描述的解决方案广告 Specify different validation groups for each item of a collection in Symfony 3?,因为在单个方法中重写整个 validation_groups 回调非常无效,并且它将组应用于所有子实体。

@Assert\Validcascade_validation 的行为不同,有没有办法在 symfony 3.4 中处理嵌入表单的单个实体 validation_groups 或者该功能肯定消失了?

【问题讨论】:

你修复了吗?我在 sf 5.1 遇到完全相同的问题 刚刚发现他们会尊重根表单上添加的任何验证组,也许子表单上的回调运行并返回正确的组列表,它们在某种程度上被根表单的验证组覆盖. 可悲的是,他们称其为功能:github.com/symfony/symfony/issues/31441 我能找到的唯一方法是添加回调 【参考方案1】:

因为这是一个预期的行为(整个表单应该从根验证组验证,你可以在这里看到解释:https://github.com/symfony/symfony/issues/31441)我能找到解决这个问题的唯一方法是在收藏品(实体):

src/AppBundle/Entity/Address.php

//...
class Address

    //...
    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="user")
     */
    protected $user;

    /**
     * @ORM\Column(type="text", nullable="true")
     */
    protected $zipCode;
    //...

    /**
     * @Assert\Callback()
     */
    public function validate(): void 
        if ($data->doesRequireZip()) 
            // validate if $zipCode isn't null
            // other validations ...  
        
    

更多关于 symfony 回调断言:https://symfony.com/doc/current/reference/constraints/Callback.html

这个解决方案是在 symfony 5.1 中制作的,但这可能适用于 2.8+

更新

只是为了添加我的最终解决方案是通过验证器添加验证,强制每个属性针对特定组(如果您的表单已经这样做,您也不需要强制默认)。

/**
 * @Assert\Callback()
 */
public function validate(ExecutionContext $context): void

    $validator = $context->getValidator();
    $groups = $this->doesRequireZip() ? ['requiredZipGroup'] : [];
    $violations = $validator->validate($this, null, $groups);

    /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
    foreach ($violations as $violation) 
        $context->buildViolation($violation->getMessage(), $violation->getParameters())
            ->atPath($violation->getPropertyPath())
            ->addViolation();
    

null 传递给$validator->validate() 的第二个参数将强制Assert\Valid 运行对象$this,因此,$groups 中的所有约束和回调都将运行

【讨论】:

我已经有一段时间不用使用这个特定的解决方案了。但是您的解决方案看起来很棒。无论如何,他们改变了这种行为仍然太糟糕了。

以上是关于在 Symfony 3 中为集合的每个元素应用特定的验证组的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 4 中为 UICollectionView 的部分设置最大元素数?

如何在循环symfony2中为同一实体添加重复的表单

在 Selenium 中为每个元素使用多个定位器的优缺点?

在jquery中为某个类的每个元素运行一个if语句

在 symfony 中为 @babel/plugin-proposal-class-properties 启用 classProperties

Symfony DomCrawler:查找具有特定属性值的元素