squirrelphp / validator-cascade
Symfony Validator 的级联属性,以更灵活和易懂的方式重新实现了 Valid 约束
Requires
- php: >=8.0
- symfony/validator: ^6.0|^7.0
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.3
- captainhook/plugin-composer: ^5.0
- mockery/mockery: ^1.0
- phpunit/phpunit: ^10.0
README
重新实现了 Symfony validator 组件中的 Valid
约束,作为 Cascade
属性,使用起来比 Valid
更直接,且没有令人惊讶的行为。
此组件与 5.x 版本的 Symfony validator 组件兼容(v2.x 支持注解/属性),与 6.x/7.x 版本兼容(v3.x 仅支持属性)并且将适应支持 Symfony 的未来版本(如果需要,将进行更改)。
安装
composer require squirrelphp/validator-cascade
目录
使用方法
Cascade
是一种约束验证,确保对象或对象数组通过 Symfony validator 组件进行验证,从而实现级联验证。
只有两个选项
-
groups
定义Cascade
约束属于哪个验证组,与任何常规验证约束的行为相同。如果不定义groups
,则默认设置为Default
。只有当其中一个验证组匹配时,Cascade
约束才会执行。 -
trigger
定义要触发的子对象上的验证组。默认情况下仅触发Default
,因此如果您想触发其他验证组,您必须使用trigger
指定它们。父对象的验证组永远不会级联。
就是这样!
示例
以下是一个实际应用中的常见示例:您可能有一个订单和多个可能的订单地址(一个用于发货,一个用于发票)以及不同的要求,某些地址可能为可选,但如果指定,则应进行验证。
$shippingAddress
展示了如何在子对象中触发特定验证组,在这种情况下,使电话号码成为信息的一部分的强制要求(通常适用于发货,但通常对于其他用途不是必需的),除了“Default”约束。
$invoiceAddress
仅在将验证组“alternateInvoiceAddress”传递给验证器时进行验证(如果用户选择了类似于“选择不同的发票地址”的选项)。电话号码是可选的,因为我们没有传递 trigger
选项,因此仅在 Address 对象中验证 Default 组。
use Squirrel\ValidatorCascade\Cascade; use Symfony\Component\Validator\Constraints as Assert; class Order { /** * Validate $shippingAddress if validation with no validation * group or the "Default" validation group is triggered * * Validates "Default" and "phoneNumberMandatory" validation groups in $shippingAddress */ #[Cascade(trigger: ['Default', 'phoneNumberMandatory'])] public Address $shippingAddress; /** * Validate $invoiceAddress only if validation group * "alternateInvoiceAddress" is passed to validator * * Validates only "Default" validation group in $invoiceAddress, so phone number is optional */ #[Assert\NotNull(groups: ['alternateInvoiceAddress'])] #[Cascade(groups: ['alternateInvoiceAddress'])] public ?Address $invoiceAddress = null; } class Address { #[Assert\Length(min: 1, max: 50)] public string $street = ''; #[Assert\Length(min: 1, max: 50)] public string $city = ''; #[Assert\Length(min: 1, max: 50, groups: ['phoneNumberMandatory'])] public string $phoneNumber = ''; } $order = new Order(); $order->shippingAddress = new Address(); $order->invoiceAddress = new Address(); // This validates with the "Default" validation group, // so only shippingAddress must be specified $symfonyValidator->validate($order); // This also validates the invoice address in addition // to the shipping address $symfonyValidator->validate($order, null, [ "Default", "alternateInvoiceAddress", ]);
为什么不使用 Valid
约束?
当涉及到验证组时,Symfony validator 组件中 Valid
约束的实现存在严重限制,并且与其他约束的行为不同。
没有验证组的 Valid
约束
#[Assert\Valid] public $someobject;
上述代码看起来像一个常规断言,但其行为不同
- 无论您给验证器提供什么验证组,断言总是会执行
- 因此,该断言不属于“Default”组
对于简单的对象或您不需要任何验证组的情况,这很好,但它仍然与其他断言不同,因为即使您以后添加验证组,也无法“跳过”此约束。
具有验证组的 Valid
约束
#[Assert\Valid(groups: ['invoice'])] public $someobject;
上面的 Valid
断言仅在您验证“invoice”验证组时才会触发,这是您所期望的。然而,有很多意想不到的行为
- 它只触发$someobject中的“发票”验证组,没有其他验证组传递给该对象(例如,如果您正在验证“默认”和“发票”组,则“默认”组永远不会到达$someobject,只有“发票”组)
- 无法更改在$someobject中触发的验证组
- 当定义验证组时,不使用
Valid
的“遍历”选项。尽管在一般情况下,“遍历”选项可能不应该使用或需要
将验证组同时用作触发器和过滤器会严重限制其使用方式,并且使得大多数使用情况(例如我们的地址示例)无法使用Valid
实现。即使您设法使其工作,您的代码也不会自解释,并且容易出错或误解属性。
在此组件中定义的Cascade
区分了约束属于哪个验证组以及子对象中触发的哪些验证组。它无法做到的是将父对象的验证组级联到子对象,因为此信息仅在验证组件的RecursiveContextualValidator
类中可用,并且无法在不改变验证组件内部大量内容的情况下访问(遗憾的是)。