Применить конкретную группу проверки для каждого элемента коллекции в Symfony 3

Мне пришлось обновить один из моих проектов с 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;
    //...
}

UserForm

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

я добавил @Assert\Valid() Пользователь::$zipCode объявление и удалил 'cascade_validation' => true (не в методе configureOptions, но кажется, что он не используется), поскольку он устарел.

Но теперь, когда добавлено 3 адреса, два должны проверить группу zipRequired, а один - нет: используются только класс пользователя validator_groups, поэтому я могу проверить форму с несогласованными данными!

Я проверил с помощью xdebug и validator_groups обратный звонок в AddressForm называется, но валидаторы нет.

Я протестировал решения, описанные здесь: Укажите разные группы проверки для каждого элемента коллекции в Symfony 2? но это не может работать больше, как в Symfony 3.4 cascade_validation на имущество выдает ошибку

В моей ситуации логика слишком сложна, чтобы использовать решение, описанное здесь. Укажите разные группы проверки для каждого элемента коллекции в Symfony 3? так как переписать весь текст крайне неэффективно validation_groups обратный вызов в отдельных методах, и он применяет группы ко всем дочерним объектам.

Поведение @Assert\Valid а также cascade_validation различаются, есть ли способ обработать встраиваемую форму отдельного объекта validation_groups в symfony 3.4 или эта функция определенно пропала?

0 ответов

Поскольку это ожидаемое поведение (вся форма должна быть проверена из корневой группы 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, поэтому все ограничения, а также обратные вызовы внутри $groups будет работать

Другие вопросы по тегам