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

Jih*_*hel 5 php forms validation symfony symfony-3.4

我不得不将我的一个项目从 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;
    //...
}
Run Code Online (Sandbox Code Playgroud)

用户表单

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
    ]);
}
Run Code Online (Sandbox Code Playgroud)

地址

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;
    //...
}
Run Code Online (Sandbox Code Playgroud)

地址表格

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;
        },
    ]);
}
Run Code Online (Sandbox Code Playgroud)

在 symfony 2.8

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

在 symfony 3.4

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

但是现在添加了 3 个地址,两个应该使 zipRequired 组有效,一个不是:仅使用用户的类 validator_groups,因此我可以验证数据不一致的表单

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

我测试了此处描述的解决方案:为 Symfony 2 中集合的每个项目指定不同的验证组?但它不能再工作了,因为在 symfony 3.4cascade_validation中的属性会引发错误

在我的情况下,所涉及的逻辑太重,无法使用此处描述的解决方案广告为 Symfony 3 中集合的每个项目指定不同的验证组?因为validation_groups在单个方法中重写整个回调并将组应用于所有子实体是非常低效的。

的行为@Assert\Validcascade_validation是不同的,有没有办法来处理的symfony 3.4嵌入形式单独实体validation_groups或功能肯定是哪里去了?

Ale*_*iro 3

由于这是预期的行为(整个表单应该从根validation_groups进行验证,您可以在这里看到解释: 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 ...  
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有关 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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