跳到内容

如何顺序应用验证组

编辑此页

在某些情况下,您希望按步骤验证您的组。为此,您可以使用 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 的实现,甚至将组提供器注册为服务。

以下是如何实现此目的

  1. 定义单独的组提供器类: 创建一个实现 GroupProviderInterface 的类,并处理动态组序列逻辑;
  2. 使用提供器配置用户:GroupSequenceProvider 属性中使用 provider 选项,将实体与提供器类链接;
  3. 自动装配或手动标记: 如果启用了 自动装配,您的自定义提供器将自动链接。否则,您必须使用 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 更直接地为您解决此问题。

这项工作,包括代码示例,已获得 Creative Commons BY-SA 3.0 许可协议的许可。
目录
    版本