Применить конкретную группу проверки для каждого элемента коллекции в 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
будет работать