如何顺序应用验证组
在某些情况下,您希望按步骤验证您的组。为此,您可以使用 GroupSequence
功能。在这种情况下,一个对象定义一个组序列,该序列确定组应被验证的顺序。
例如,假设您有一个 User
类,并且希望仅当所有其他验证都通过时才验证用户名和密码是否不同(以避免多条错误消息)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// src/Entity/User.php
namespace App\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
#[Assert\GroupSequence(['User', 'Strict'])]
class User implements UserInterface
{
#[Assert\NotBlank]
private string $username;
#[Assert\NotBlank]
private string $password;
#[Assert\IsTrue(
message: 'The password cannot match your username',
groups: ['Strict'],
)]
public function isPasswordSafe(): bool
{
return ($this->username !== $this->password);
}
}
在此示例中,它将首先验证组 User
中的所有约束(这与 Default
组相同)。仅当该组中的所有约束都有效时,才会验证第二个组 Strict
。
警告
正如您在 如何仅应用所有验证约束的子集(验证组) 中已经看到的,Default
组和包含类名的组(例如 User
)是相同的。但是,当使用组序列时,它们不再相同。Default
组现在将引用组序列,而不是不属于任何组的所有约束。
这意味着在指定组序列时,您必须使用 {ClassName}
(例如 User
) 组。当使用 Default
时,您会得到无限递归(因为 Default
组引用组序列,该序列将包含 Default
组,而 Default
组又引用相同的组序列,...)。
警告
使用序列中的组(上例中的 Strict
)调用 validate()
将仅导致对该组进行验证,而不是对序列中的所有组进行验证。这是因为序列现在被引用为 Default
组验证。
您还可以在 validation_groups
表单选项中定义组序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Form/MyType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\GroupSequence;
// ...
class MyType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => new GroupSequence(['First', 'Second']),
]);
}
}
组序列提供器
想象一下一个 User
实体,它可以是普通用户或高级用户。当它是高级用户时,应向用户实体添加一些额外的约束(例如,信用卡详细信息)。要动态确定应激活哪些组,您可以创建一个组序列提供器。首先,创建实体和一个名为 Premium
的新约束组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Entity/User.php
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
#[Assert\NotBlank]
private string $name;
#[Assert\CardScheme(
schemes: [Assert\CardScheme::VISA],
groups: ['Premium'],
)]
private string $creditCard;
// ...
}
现在,更改 User
类以实现 GroupSequenceProviderInterface 并添加 getGroupSequence() 方法,该方法应返回要使用的组数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Entity/User.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\GroupSequenceProviderInterface;
class User implements GroupSequenceProviderInterface
{
// ...
public function getGroupSequence(): array|GroupSequence
{
// when returning a simple array, if there's a violation in any group
// the rest of the groups are not validated. E.g. if 'User' fails,
// 'Premium' and 'Api' are not validated:
return ['User', 'Premium', 'Api'];
// when returning a nested array, all the groups included in each array
// are validated. E.g. if 'User' fails, 'Premium' is also validated
// (and you'll get its violations too) but 'Api' won't be validated:
return [['User', 'Premium'], 'Api'];
}
}
最后,您必须通知验证器组件,您的 User
类提供要验证的组序列
1 2 3 4 5 6 7 8 9 10
// src/Entity/User.php
namespace App\Entity;
// ...
#[Assert\GroupSequenceProvider]
class User implements GroupSequenceProviderInterface
{
// ...
}
高级验证组提供器
在上一节中,您学习了如何基于实体的状态动态更改组序列。但是,在更高级的情况下,您可能需要使用一些外部配置或服务来定义该组序列。
管理实体初始化和手动设置其依赖项可能很麻烦,并且实现可能与实体职责不一致。为了解决这个问题,您可以在实体外部配置 GroupProviderInterface 的实现,甚至将组提供器注册为服务。
以下是如何实现此目的
- 定义单独的组提供器类: 创建一个实现 GroupProviderInterface 的类,并处理动态组序列逻辑;
- 使用提供器配置用户: 在 GroupSequenceProvider 属性中使用
provider
选项,将实体与提供器类链接; - 自动装配或手动标记: 如果启用了 自动装配,您的自定义提供器将自动链接。否则,您必须使用
validator.group_provider
标签手动标记您的服务。
1 2 3 4 5 6 7 8 9 10 11
// src/Entity/User.php
namespace App\Entity;
// ...
use App\Validator\UserGroupProvider;
#[Assert\GroupSequenceProvider(provider: UserGroupProvider::class)]
class User
{
// ...
}
通过这种方法,您可以在实体结构和组序列逻辑之间保持清晰的分离,从而实现更高级的用例。
如何顺序应用单个属性的约束
有时,您可能希望在单个属性上顺序应用约束。Sequentially 约束 可以比使用 GroupSequence
更直接地为您解决此问题。