在Symfony 3

时间:2018-03-07 14:09:48

标签: php forms symfony validation symfony-3.4

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

为了简化,我们说我有一个用户实体,有许多地址实体。 当我创建/更新我的用户时,我希望能够添加/删除/更新任意数量的地址。所以在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;
    //...
}

为AddressForm

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组,而不是一个:仅使用用户的类validator_groups,所以我可以使用不连贯数据的表单有效

我使用xdebug进行了检查,validator_groups中的AddressForm回调被调用,但验证器未被调用。

我测试了这里描述的解决方案: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,或者该功能肯定已经消失?

1 个答案:

答案 0 :(得分:1)

由于这是预期的行为(应从根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 ...  
        }
    }
}

有关symfony回调断言的更多信息:https://symfony.com/doc/current/reference/constraints/Callback.html

此解决方案是在symfony 5.1中创建的,但可能从2.8+起可以使用

更新

为了添加我的最终解决方案,是通过验证器添加验证,将每个属性都强制加入一个特定的组(同样,如果您的表单已经这样做,则也不必强制使用Default)。

/**
 * @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,因此,{{1}中的所有约束以及回调}将运行