workup/state-machine

Winzou State Machine 服务提供商,适用于 Laravel

v3.4.4.001 2024-04-03 16:53 UTC

README

这是一个为 winzou/state-machine 提供的 Laravel 服务提供者。它提供了对 StateMachineFactory 的依赖注入。您还可以使用 Laravel 的服务容器来解析回调类的类方法。还提供了一个门面以方便使用。

安装

您可以通过 composer 安装此包。

然后使用命令行界面要求安装此包

composer require workup/state-machine

版本

如果您需要在较旧的 Laravel 安装中安装此包,请使用下表找到兼容的版本。

自 5.5 版本以来,Laravel 使用包自动发现,因此您不需要手动添加 ServiceProvider 和门面。如果您不使用自动发现或您正在使用较旧版本,请在 config/app.php 中添加服务提供者和门面。

<?php

'providers' => [
     Workup\StateMachine\ServiceProvider::class,
],

'aliases' => [
    'StateMachine' =>  Workup\StateMachine\Facade::class,
],

配置

有两种方式可以配置状态机

通用配置文件

config/state-machine.php 中发布配置文件(如果您在模型中使用了提供的 trait,则不需要此步骤)。

php artisan vendor:publish --provider="Workup\StateMachine\ServiceProvider"

模型 trait

在您的模型中添加 HasStateMachines trait 并实现 $stateMachines 数组来自动设置其状态机。

protected static array $stateMachines = [
    'state' => 'state-machine-config', // passing a string will load the corresponding config
    'another-state' => [               // inline declaration
        'states' => ['test'],
        'transitions' => [],
    ],
];

选项

有关所有可用选项的文档,请参阅 StateMachineBundle 的文档。

使用时

当使用您模型中的提供的 trait 时,您不需要传递 graphproperty_nameclass 参数。您还可以传递 initial_state 参数(默认情况下,初始状态将是状态数组中找到的第一个)。

<?php

// Get the article
$article = App\Article::find($id);

// Get the state machine for this article, and graph called "simple"

// Using the facade
$stateMachine = StateMachine::get($article, 'simple');

// Or using the service container with dependency injection
public function method(SM\Factory\FactoryInterface $factory)
{
    $stateMachine = $factory->get($article, 'simple');
}

现在您可以使用 $stateMachine 来与 $article 的状态进行交互。

<?php

// Get the actual state of the object
$stateMachine->getState();

// Get all available transitions
$stateMachine->getPossibleTransitions();

// Check if a transition can be applied: returns true or false
$stateMachine->can('approve');

// Apply a transition: returns true or throws an SM\SMException
$stateMachine->apply('publish');

// Apply a transition without throwing an exception: returns true or false
$stateMachine->apply('publish', true);

回调

回调用于保护转换或在实际应用转换之前或之后执行某些代码。此包增加了使用 Laravel 的服务容器来解决回调的能力。

例如。

您想要调用 MyService 类的 handle 方法以确定状态机是否可以应用 submit_changes 转换。

<?php

'callbacks' => [
    // will be called when testing a transition
    'guard' => [
        'guard_on_submitting' => [
            // call the callback on a specific transition
            'on' => 'submit_changes',

            // will call the method of this class
            'do' => ['MyService', 'handle'],

            // arguments for the callback
            'args' => ['object'],
        ],
    ],
],

您可以使用数组格式或 @ 分隔的字符串格式指定回调,例如 ['Class', 'method']Class@method

使用权限和策略

如果您不想使用自定义类和方法进行保护,则可以使用 Laravel 的授权权限或策略来决定是否可以应用转换。

您必须定义一个 can 键,而不是指定 do 键,以检查您想要检查的能力的名称。自 Laravel 5.5 以来,您还可以指定一组能力,并将逐一进行检查。

默认情况下,对象实例将被传递给权限作为参数。您还可以通过指定 args 键来覆盖这些参数。

使用权限的示例

在这个示例中,我们定义了一个将接受 $article 作为参数的权限。如果您不需要此参数,则不需要在您的权限中定义此参数。

<?php

use App\User;
use App\Article;

Gate::define('approve', function (User $user, Article $article) {
    //
});
<?php

'callbacks' => [
    'guard' => [
        'guard_on_approving' => [
            // call the gate on a specific transition
            'on' => 'approve',
            // will call Gate:allows('approve', $article)
            'can' => 'approve',
        ],
    ],
],

使用策略的示例

假设您为您的 Article 模型创建了一个 ArticlePolicy 策略,其中包含一个 approve 方法。

您应该在 can 指数中定义 approve。这将相当于调用 $user->can('approve', $article)

您还可以通过指定 args 数组来覆盖将传递给 approved 方法的参数。为了使策略类能够解析,您必须将 object 作为第一个参数。例如:'args' => ['object', '"final_approval"'] 将相当于调用 $user->can('approve', [$article, 'final_approval'])

<?php

namespace App\Policies;

use App\User;
use App\Article;

class ArticlePolicy
{
    public function approve(User $user, Article $article)
    {
        //
    }
}
<?php

'callbacks' => [
    'guard' => [
        'guard_on_approving' => [
            // call the policy on a specific transition
            'on' => 'approve',
            // will call Gate:allows('approve', $article)
            'can' => 'approve',
        ],
    ],
],

事件

在检查是否可以应用转换时,会触发 SM\Event\SMEvents::TEST_TRANSITION 事件。

在应用转换之前和之后,分别会触发 SM\Event\SMEvents::PRE_TRANSITIONSM\Event\SMEvents::POST_TRANSITION 事件。

所有事件都会接收到一个 Workup\SM\Event\TransitionEvent 实例。

如果您想使用相同的监听器监听所有事件,可以使用 winzou.state_machine.* 通配符参数。

您可以在应用程序的 EventServiceProvider 中定义自己的监听器。例如:

<?php

use SM\Event\SMEvents;

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    SMEvents::TEST_TRANSITION => [
        \App\Listeners\CheckTransition::class,
    ],
    SMEvents::PRE_TRANSITION => [
        \App\Listeners\BeforeTransition::class,
    ],
    SMEvents::POST_TRANSITION => [
        \App\Listeners\AfterTransition::class,
    ],
    'winzou.state_machine.*' => [
        \App\Listeners\Transition::class,
    ],
];

上下文

在检查或应用转换时,您还可以通过数组传递额外的数据。此数组将被传递到 Workup\SM\Event\TransitionEvent。您可以在事件监听器或回调中使用 $event->getContext() 访问数组。

使用事件监听器的示例

<?php

// Reject the transition of the approval date is past
Event::listen(SMEvents::TEST_TRANSITION, function (TransitionEvent $event) {
    $context = $event->getContext();

    if ($context['approved_at']->isPast()) {
        $event->setRejected();
    }
});

// Check if a approve transition can be applied on some date
$stateMachine->can('approve', ['approved_at' => now()]);

使用回调的示例

<?php

// Setup an callback after publishing
[
    'callbacks' => [
        'after' => [
            'after_publishing' => [
                'on' => 'publish',
                'do' => [App\Actions\PublishArticleAction::class, 'execute'],
                'args' => ['object', 'event'],
            ],
        ],
    ],
];

// Save the publish date in your action
class PublishArticleAction
{
    public function execute(Article $article, TransitionEvent $event)
    {
        $context = $event->getContext();
        $article->update(['published_at' => $context['published_at']]);
    }
}

// Apply a publish transition on some date
$stateMachine->apply('publish', false, ['published_at' => now()]);

元数据

您可以选择在图、状态和转换中存储元数据。元数据存储在关联数组中,可以是任何您想要的内容。

<?php

return [
    'graphA' => [
        'class' => App\Article::class,

        'metadata' => [
            'title' => 'Article State Machine',
        ],

        'states' => [
            [
                'name' => 'pending_review',
                'metadata' => ['title' => 'Pending Review'],
            ],
        ],

        'transitions' => [
            'ask_for_changes' => [
                'from' => ['pending_review'],
                'to' => 'accepted',
                'metadata' => ['title' => 'Ask for changes'],
            ],
        ],
    ],
];

状态机对象提供了许多灵活的方法来获取元数据,无论是作为关联数组,还是通过键获取特定值。如果没有指定的键,您还可以传递默认值或闭包。

从图中获取元数据

<?php

$stateMachine = StateMachine::get($article);

// ['title' => 'Article State Machine']
$stateMachine->metadata('graph');
$stateMachine->metadata()->graph();

// 'Article State Machine'
$stateMachine->metadata('title');

// 'Article State Machine'
$stateMachine->metadata('graph', 'title');
$stateMachine->metadata()->graph('title');

// null
$stateMachine->metadata('graph', 'invalid');
$stateMachine->metadata()->graph('invalid');

// 'default'
$stateMachine->metadata('graph', 'invalid', 'default');
$stateMachine->metadata()->graph('invalid', 'default');

从状态中获取元数据

$stateMachine = StateMachine::get($article);

// ['title' => 'Pending Review']
$stateMachine->metadata('state', 'pending_review');
$stateMachine->metadata()->state('pending_review');

// 'Pending Review'
$stateMachine->metadata('state', 'pending_review', 'title');
$stateMachine->metadata()->state('pending_review', 'title');

// null
$stateMachine->metadata('state', 'pending_review', 'invalid');
$stateMachine->metadata()->state('pending_review', 'invalid');

// 'default'
$stateMachine->metadata('state', 'pending_review', 'invalid', 'default');
$stateMachine->metadata()->state('pending_review', 'invalid', 'default');

从当前状态中获取元数据

<?php

$article->state = 'pending_review';
$stateMachine = StateMachine::get($article);

// ['title' => 'Pending Review']
$stateMachine->metadata('state');

// 'Pending Review'
$stateMachine->metadata('state', 'title');

// null
$stateMachine->metadata('state', 'invalid');

// 'default'
$stateMachine->metadata('state', 'invalid', 'default');

从转换中获取元数据

<?php

$stateMachine = StateMachine::get($article);

// ['title' => 'Ask for changes']
$stateMachine->metadata('transition', 'ask_for_changes');
$stateMachine->metadata()->transition('ask_for_changes');

// 'Ask for changes'
$stateMachine->metadata('transition', 'ask_for_changes', 'title');
$stateMachine->metadata()->transition('ask_for_changes', 'title');

// null
$stateMachine->metadata('transition', 'ask_for_changes', 'invalid');
$stateMachine->metadata()->transition('ask_for_changes', 'invalid');

// 'default'
$stateMachine->metadata('transition', 'ask_for_changes', 'invalid', 'default');
$stateMachine->metadata()->transition('ask_for_changes', 'invalid', 'default');

调试命令

包含了一个用于调试图的 artisan 命令。它接受图的名称作为参数。如果没有传递参数,将会交互式地询问图名。

$ php artisan winzou:state-machine:debug simple

+--------------------+-----------------------+
| Configured States: | Metadata:             |
+--------------------+-----------------------+
| new                |                       |
| pending_review     | title: Pending Review |
| awaiting_changes   |                       |
| accepted           |                       |
| published          |                       |
| rejected           |                       |
+--------------------+-----------------------+

+-----------------+------------------+------------------+
| Transition      | From(s)          | To               |
+-----------------+------------------+------------------+
| create          | new              | pending_review   |
+-----------------+------------------+------------------+
| ask_for_changes | pending_review   | awaiting_changes |
|                 | accepted         |                  |
+-----------------+------------------+------------------+
| cancel_changes  | awaiting_changes | pending_review   |
+-----------------+------------------+------------------+
| submit_changes  | awaiting_changes | pending_review   |
+-----------------+------------------+------------------+
| approve         | pending_review   | accepted         |
|                 | rejected         |                  |
+-----------------+------------------+------------------+
| publish         | accepted         | published        |
+-----------------+------------------+------------------+

+---------------------+--------------------+------------------------+--------+
| Guard Callbacks     | Satisfies          | Do                     | Args   |
+---------------------+--------------------+------------------------+--------+
| guard_on_submitting | On: submit_changes | MyClass::handle()      | object |
| guard_on_approving  | On: approve        | Gate::check("approve") |        |
+---------------------+--------------------+------------------------+--------+

+---------------------+-------------+-------------+-----------------------------+
| Before Callbacks    | Satisfies   | Do          | Args                        |
+---------------------+-------------+-------------+-----------------------------+
| log_before_approval | On: approve | Log::info() | "approving article", object |
+---------------------+-------------+-------------+-----------------------------+

+----------------------+--------------+------------------------------+---------------+
| After Callbacks      | Satisfies    | Do                           | Args          |
+----------------------+--------------+------------------------------+---------------+
| email_after_approval | To: accepted | SendApprovalMail::dispatch() | object, event |
+----------------------+--------------+------------------------------+---------------+

可视化命令

包含了一个用于生成给定图图像的 artisan 命令。它接受图的名称作为参数。它是从相应的 Symfony 包 https://github.com/MadMind/StateMachineVisualizationBundle 中获得的,所以所有的荣誉都归原作者。

如果您想运行此命令,您需要安装 dot - 图形viz 包的一部分(https://graphviz.cn/)。在您的 mac 上,这相当于运行了 brew install graphviz

php artisan winzou:state-machine:visualize {graph? : A state machine graph} {--output=./graph.jpg} {--format=jpg} {--direction=TB} {--shape=circle} {--dot-path=/usr/local/bin/dot}

test

Eloquent 模型的可状态特性

如果您想在模型中直接与状态机交互,可以通过 laravel-statable 包来实现,该包由 iben12 提供。

此包允许您从模型中获取图,检查/应用转换,以及在数据库中记录状态历史。

变更日志

有关最近更改的更多信息,请参阅 CHANGELOG

测试

$ composer test

贡献

有关详细信息,请参阅 CONTRIBUTING

安全性

如果您发现任何安全相关的问题,请通过电子邮件 info@sebdesign.eu 而不是使用问题跟踪器。

鸣谢

许可

MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件