jkbennemann/laravel-foliage

此包允许您在应用程序中对任意业务需求与定义的规则集进行验证。

0.1.1 2024-02-25 14:19 UTC

This package is auto-updated.

Last update: 2024-09-25 15:35:21 UTC


README

此包允许您在应用程序中对任意业务需求进行验证。

Laravel Foliage Logo

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

可以验证什么?

基本上,只要你能从中构造出逻辑表达式,你就可以验证任何内容。

示例

  • 通过基于您的业务规则的扩展验证来扩展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)。有关更多信息,请参阅许可文件