jkbennemann / laravel-foliage
此包允许您在应用程序中对任意业务需求与定义的规则集进行验证。
Requires
- php: ^8.1|^8.2
- illuminate/contracts: ^10.0
- spatie/laravel-data: ^3.11
- spatie/laravel-package-tools: ^1.14.0
Requires (Dev)
- barryvdh/laravel-ide-helper: ^2.15
- larastan/larastan: ^2.0.1
- laravel/pint: ^1.0
- nunomaduro/collision: ^7.8
- orchestra/testbench: ^8.8
- pestphp/pest: ^2.20
- pestphp/pest-plugin-arch: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- spatie/laravel-ray: ^1.26
README
此包允许您在应用程序中对任意业务需求进行验证。
可以验证什么?
基本上,只要你能从中构造出逻辑表达式,你就可以验证任何内容。
示例
- 通过基于您的业务规则的扩展验证来扩展Laravel Gates/Policies
- 验证存储在数据库模型上的规则,例如:优惠券代码可用性、订阅验证等。
- 在您的类中根据例如请求输入等执行任何其他任意数据验证。
- ...
您的选择几乎是无限的
安装
您可以通过composer安装此包
composer require jkbennemann/laravel-foliage
您可以使用以下命令发布配置文件
php artisan vendor:publish --tag="foliage-config"
这是发布配置文件的内容
return [ 'available_rules' => [ //add your rules here //SampleRule::class, ], 'rule_parser' => \Jkbennemann\Foliage\Core\RuleParser::class, 'payload_namespace' => 'App\Services\Foliage\Payloads', 'rules_namespace' => 'App\Services\Foliage\Rules', 'validator' => \Jkbennemann\Foliage\Validator\TreeValidator::class, 'validation_data_builder' => \Jkbennemann\Foliage\Validator\ValidationDataBuilder::class, ];
工作原理
此包的整体概念是启用业务规则的任意验证。
为了实现这一点,我们将从您的规则集中创建一个二叉树。
然后,可以通过给定的一组数据进行验证。
对于每个规则,将自动从提供的有效载荷中获取用于验证所需的数据。
每个树节点最终将表示为一个包含以下数据的数组。
[ 'alias' => null, 'children' => [ //sub nodes ], 'data' => [ //data of your rule ], 'name' => 'rule_key', 'operation' => 'AND|OR|NOT|null', 'type' => 'node|leaf', ]
使用方法
实例化新规则
要创建新的规则,您可以使用包提供的类或只需使用外观(Facade)来获得简单且易于表达的API。
基本实例化
$availableRules = config('foliage.available_rules'); $ruleParser = new \Jkbennemann\Foliage\Core\RuleParser($availableRules); $foliage = new Jkbennemann\Foliage\Core\Rule($ruleParser); $rule = $foliage->single(SampleRule::class, ['sample' => 'data']); $rule = $foliage->and([ [SampleRule::class, ['sample' => 'data']), [AnotherRule::class, ['another_sample' => 'data']), ]); $rule = $foliage->or([ [SampleRule::class, ['sample' => 'data']), [AnotherRule::class, ['another_sample' => 'data']), ]);
外观使用
use Jkbennemann\Foliage\Facades\Rule; //Single rule usage $rule = Rule::single( SampleRule::class, ['sample' => 'data'] ); $rule = Rule::not( SampleRule::class, ['sample' => 'data'] ); //Multi rule usage $rule = Rule::and( Rule::single(SampleRule::class, ['sample' => 'data']), Rule::not(AnotherRule::class, ['another_sample' => 'data']), ); $rule = Rule::or( Rule::single(SampleRule::class, ['sample' => 'data']), Rule::not(AnotherRule::class, ['another_sample' => 'data']), Rule::and( Rule::not(ThirdRule::class, ['another_sample' => 'data']), Rule::not(ThirdRule::class, ['another_sample' => 'data']), ) );
一个简单的 and
规则的示例表示如下
$rule = Rule::and( Rule::single(SampleRule::class, ['sample' => 'data']), Rule::not(AnotherRule::class, ['another_sample' => 'data']), ); $structure = $rule->toArray(); echo $rule->jsonSerialize();
{ "alias": null, "children": [ { "alias": null, "children": [], "data": { "sample": "data" }, "name": "sample_rule", "operation": null, "type": "leaf" }, { "alias": null, "children": [], "data": { "another_sample": "data" }, "name": "another_rule", "operation": null, "type": "leaf" } ], "data": null, "name": null, "operation": "AND", "type": "node" }
为规则有效载荷使用别名
如果您想多次使用相同的规则,但具有不同的选项,您需要为该规则指定一个别名。
别名基本上覆盖了用于验证有效载荷的参数名称。
假设您作为管理员代表您的应用程序中的用户执行操作。
您作为管理员有权这样做,但考虑到的用户本身没有。
$payload = [ 'user_is_admin' => $user->isAdmin(), 'performing_user_is_admin' => $currentLoggedInUser->isAdmin(), ]; $rule = Rule::or( Rule::single(IsAdminRule::class)->alias('performing_user_is_admin'), Rule::and( Rule::not(IsAdminRule::class)->alias('user_is_admin'), Rule::not(IsAllowedUser::class, ['user' => 'allowed_user']), ), );
通过结构化树数据创建规则
最终,您想将树结构存储在您的数据库中以验证Eloquent模型。
在这种情况下,您的模型应该实现包提供的 HasValidationRules
特性。
这使您能够访问这些规则。
如果您使用MySQL/MariaDB,则您的数据库字段应该是 json
字段。
use Jkbennemann\Foliage\Core\HasValidationRules; class CouponCode extends Model { use HasValidationRules; protected $casts = [ 'database_field_name' => 'array', ]; //.. protected function rulesFieldName(): string { return 'database_field_name'; } }
现在您的模型可以访问
$node = $coupon->validationNode(); //returns a Node object for validation
从现有数组
如果您有一个已经处于树结构的数组,您可以像这样创建一个节点
$ruleData = [ 'alias' => null, 'children' => [ [ 'alias' => null, 'children' => [], 'data' => [ 'foo' => 'bar', ], 'name' => 'rule_1', 'operation' => null, 'type' => 'leaf', ], [ 'alias' => null, 'children' => [], 'data' => [ 'bar' => 'baz', ], 'name' => 'rule_2', 'operation' => null, 'type' => 'leaf', ], ], 'data' => null, 'name' => null, 'operation' => 'AND', 'type' => 'node', ] $builder = app(\Jkbennemann\Foliage\Core\TreeBuilder::class); $node = $builder->build($ruleData);
验证规则
要验证创建的规则集,您可以使用以下方法
- 调用Foliage类的
validate()
方法 - 创建自己的验证器
- 在
Node
实例上调用validate()
方法
$rule = Rule::single(SampleRule::class, ['name' => 'John Doe']); $node = $rule->node(); //payload constructed during your application's request lifecycle. $payload = [ 'name' => 'John Doe' ]; //manual instantiation //returns `Result` object $foliage = new Foliage($validator, $treeBuilder); $result = $foliage->validateSilently($node, $payload); // does not throw an exception on error $foliage->validate($node, $payload); // throws exception on error //using Facade //returns `Result` object $result = \Jkbennemann\Foliage\Facades\Foliage::validate($node, $payload) $result->isValid(); //true $result->errors(); //empty collection
异常处理
默认情况下,验证器在首次发生验证错误时抛出异常。
如果您想更改此行为,您可以指示验证器不要抛出异常。
$validator = new TreeValidator(new ValidationDataBuilder(), new PostOrderEvaluator()); $validator->withoutExceptions(); $isValid = $validator->evaluate($node, $payload); $errors = $validator->errors(); $validator->evaluate($node, $payload)
容器解析
该包使用Laravel容器,通过从配置文件 config/foliage.php
中获取设置。
因此,您也可以通过调用容器来实例化验证器。
use Jkbennemann\Foliage\Validator\Contracts\BaseValidator; $validator = app(BaseValidator::class); $validator->withoutExceptions(); $validator->withExceptions();
创建一个新的验证规则
如果您想创建一个新的规则,您可以运行 artisan
命令。
php artisan validation:create-rule SampleRule
此命令在配置文件中指定的命名空间内创建一个新的规则。
内容将是
<?php declare(strict_types=1); namespace App\Services\BusinessRequirements\Rules; use Jkbennemann\Foliage\Core\BaseValidationRule; use Jkbennemann\Foliage\Core\Payload\BaseValidationPayload; use Jkbennemann\Foliage\Exceptions\RuleValidation; class SampleRule extends BaseValidationRule { /** @throws RuleValidation */ protected function validation(BaseValidationPayload $payload): void { //your implementation } protected function key(): string { return 'sample'; } protected function inverseValidationException(BaseValidationPayload $payload): RuleValidation { throw new RuleValidation($this, 'error_message', $payload, 'custom_key'); } }
创建一个新的有效载荷类
如果您想创建一个可用于特定规则的负载类,您可以运行 artisan
命令。
php artisan validation:create-payload AvailabilityPayload
此命令将在配置文件中指定的命名空间内创建一个新的有效载荷。
内容将是
<?php declare(strict_types=1); namespace App\Services\BusinessRequirements\Payloads; use Jkbennemann\Foliage\Core\Payload\BaseValidationPayload; class AvailabilityPayload extends BaseValidationPayload { public function __construct( ) { } }
您可以为构造函数添加任何您喜欢的参数,例如
public function __construct(public \Illuminate\Support\Carbon $date) {}
现在在您的验证规则内部,您可以按照以下方式覆盖用于此规则的负载类
class SampleRule extends BaseValidationRule { /** * @param AvailabilityPayload $payload * @throws RuleValidation */ protected function validation(BaseValidationPayload $payload): void { $ruleSettings = $this->settings(); $dateNeeded = $ruleSettings['until']; if ($payload->date->lt($dateNeeded)) { return; } throw new \Jkbennemann\Foliage\Exceptions\RuleValidation($this, 'Not available', $payload); } //.. public function payloadObjectClass(): string { return AvailabilityPayload::class; } }
要构建规则,您现在可以像这样验证您的业务逻辑
$payload = [ 'date' => now(), ]; $rule = Rule::single(SampleRule::class, ['until' => Carbon::make('01-02-2024')]) $result = Foliage::validateSilently($rule->node(), $payload); $result->isValid(); $result->errors();
测试
composer test
变更日志
有关最近更改的更多信息,请参阅变更日志。
贡献
有关详细信息,请参阅贡献指南。
安全漏洞
有关如何报告安全漏洞的详细信息,请参阅我们的安全策略。
致谢
许可
MIT许可(MIT)。有关更多信息,请参阅许可文件。