为 Symfony 3 中集合的每个元素应用特定的验证组
Apply specific validation group for each element of a collection in 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;
//...
}
用户窗体
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 中
我将 @Assert\Valid()
添加到 User::$zipCode 声明并删除了 'cascade_validation' => true
(不在方法 configureOptions 中,但似乎未使用),因为它已被弃用。
但是现在添加了 3 个地址,其中两个应该使 zipRequired 组有效,而一个没有:仅使用用户的 class validator_groups, 所以我可以使 a表格数据不连贯 !
我用 xdebug 检查过 validator_groups
回调 AddressForm
被调用但验证器没有被调用。
我测试了这里描述的解决方案:Specify different validation groups for each item of a collection in Symfony 2? 但它不能再像在 symfony 3.4 中那样工作 cascade_validation
在 属性 上抛出错误
在我的情况下,所涉及的逻辑太繁重,无法使用此处描述的解决方案广告 ,因为在单个方法中重写整个 validation_groups
回调非常低效,并且它将组应用于所有子实体。
@Assert\Valid
和 cascade_validation
的行为不同,有没有办法在 symfony 3.4 中处理嵌入形式的单个实体 validation_groups或者该功能肯定消失了?
因为这是一个预期的行为(整个表单应该从根 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+
更新
只是添加我的最终解决方案是通过验证器添加验证,强制每个 属性 针对特定组(如果您的表单已经这样做,您也不需要强制默认)。
/**
* @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()
上的第二个参数将通过对象 $this
强制 Assert\Valid
到 运行,因此,所有约束和回调在 $groups
内将 运行
我不得不将我的一个项目从 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 中
我将 @Assert\Valid()
添加到 User::$zipCode 声明并删除了 'cascade_validation' => true
(不在方法 configureOptions 中,但似乎未使用),因为它已被弃用。
但是现在添加了 3 个地址,其中两个应该使 zipRequired 组有效,而一个没有:仅使用用户的 class validator_groups, 所以我可以使 a表格数据不连贯 !
我用 xdebug 检查过 validator_groups
回调 AddressForm
被调用但验证器没有被调用。
我测试了这里描述的解决方案:Specify different validation groups for each item of a collection in Symfony 2? 但它不能再像在 symfony 3.4 中那样工作 cascade_validation
在 属性 上抛出错误
在我的情况下,所涉及的逻辑太繁重,无法使用此处描述的解决方案广告 validation_groups
回调非常低效,并且它将组应用于所有子实体。
@Assert\Valid
和 cascade_validation
的行为不同,有没有办法在 symfony 3.4 中处理嵌入形式的单个实体 validation_groups或者该功能肯定消失了?
因为这是一个预期的行为(整个表单应该从根 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+
更新
只是添加我的最终解决方案是通过验证器添加验证,强制每个 属性 针对特定组(如果您的表单已经这样做,您也不需要强制默认)。
/**
* @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()
上的第二个参数将通过对象 $this
强制 Assert\Valid
到 运行,因此,所有约束和回调在 $groups
内将 运行