uuf6429/state-engine

提供状态引擎或机器的接口和基本实现的库

2.1.0 2024-09-07 16:42 UTC

This package is auto-updated.

Last update: 2024-09-07 16:44:55 UTC


README

CI codecov Minimum PHP Version License Latest Stable Version Latest Unstable Version

此库提供了一些接口和状态引擎或状态机的基实施

亮点:

  • 双重功能
    1. 作为一个基本的状态引擎;只要定义了转换,就可以切换到所需状态(见 "JiraIssueTest")
    2. 更复杂的状态机;与上面相同,但匹配任何状态的数据(见 "TurnstileTest")
  • 高度可组合 - 可以按需替换所有内容
  • PSR-14(事件分发器)兼容
  • 流畅的构建器接口(见 "From Scratch")
  • 生成 Mermaid 或 PlantUML 标记(见 "示例 & 测试")

🔌 安装

安装此库的推荐且最简单的方法是通过 Composer

composer require "uuf6429/state-engine-php"

🧐 为什么?

原则上,这样的引擎易于实现,但在实践中,通常实现得很糟糕或者被遗忘。

例如,可能有一个 is_active 字段,认为不会存在其他状态,然后后来需要 is_pending 字段,这时重构标记到状态的标志就太晚了。

无论如何,此库抽象出这种情况,或者至少减少了代码量。

🤔 如何?

有几个关键部分说明了它是如何工作的

  • 状态 - 表示模型单个状态的对象。因此,模型可能具有不同的状态级别,例如门可以有 打开关闭 状态,但它也可以是 锁定未锁定。在这种情况下,可以将门锁视为一个单独的模型(具有单独的引擎实例),或者合并所有状态:打开关闭-未锁定关闭-锁定
  • 转换 - 表示从一种状态到另一种状态的转换的对象。这就是您定义模型可以经过的各种状态流的方式。
  • 转换存储库 - 意识到并提供所有可能允许的转换的对象。
  • 引擎 - 执行模型从一个状态到另一个状态转换的对象。通常,您会在应用程序中的每个具有状态的模型上有一个引擎实例。

🚀 使用

您可以从头开始使用它,或者将其插入到现有的应用程序中。它基本上有三个部分

  1. 配置引擎(创建状态和转换)
  2. 使用引擎(例如,在 Web 控制器或服务中)
  3. (可选)处理事件(使用提供给引擎的相同事件分发器)

另一种情况是您需要提供一个有效转换的列表,例如提供给用户。在这种情况下,在存储库上使用 StateTraversion 特性将很有用。

从头开始

以下是一个使用提供的实现(假设有一个 "门" 模型)的快速且简单的示例

use App\Models\Door;  // example model that implements StateAwareInterface

use uuf6429\StateEngine\Implementation\Builder;
use uuf6429\StateEngine\Implementation\Entities\State;

$doorStateManager = Builder::create()
    ->defState('open', 'Open')
    ->defState('closed', 'Closed')
    ->defState('locked', 'Locked')
    ->defTransition('open', 'closed', 'Close the door')
    ->defTransition('closed', 'locked', 'Lock the door')
    ->defTransition('locked', 'closed', 'Unlock the door')
    ->defTransition('closed', 'open', 'Open the door')
    ->getEngine(); // you can pass an event dispatcher to the engine here

// find Door 123 (laravel-style repository-model)
$door = Door::find(123);

// close the door :)
$doorStateManager->changeState($door, new State('closed'));

从头开始(自定义)

您不喜欢引擎的工作方式?还是您觉得状态可以更详细?那么您就有福了!由于整个库基于接口,您可以轻松地替换实现的一部分。例如,您可以将状态或转换存储在数据库中,在这种情况下,您可以拥有自己的TransitionRepository,它访问数据库。

现有代码

该库提供了一些灵活性,以便您可以将现有代码与之连接。在更复杂的场景中,您可能需要构建一个小层来弥合差距。下面的示例说明了如何处理带有标志而不是单个状态的模式。

use App\Models\Door;  // example model

use uuf6429\StateEngine\Implementation\Builder;
use uuf6429\StateEngine\Implementation\Entities\State;

$door = Door::find(123);

$doorStateMutator = Builder::makeStateMutator(
    // define how we get the state
    static function () use ($door): State {
        if ($door->is_locked) {
            return new State('locked');
        }

        return $door->is_open
            ? new State('open')
            : new State('closed');
    },
    // define how we set the state
    static function (State $newState) use ($door): void {
        $door->update([
            'is_locked' => $newState->getName() === 'locked',
            'is_open' => $newState->getName() === 'open',
        ]);
    }
);

// assumes engine $doorStateManager was already defined
$doorStateManager->changeState($doorStateMutator, new State('closed'));

😎 示例与测试

您可以在readme中找到一些示例,以及测试,其中一些在下面进行了说明。

JiraIssueTest 状态引擎

此测试提供了一个如何设置Jira问题状态的实例。

该测试还生成了下面的Mermaid图表,归功于Mermaidable特质

TurnstileTest 状态机

此测试说明了如何使用有限状态机来模拟旋转门。如前所述,这里是生成的图表

这是状态机定义的外观以及它如何被使用

use App\Models\Turnstile;  // example model that implements StateAwareInterface

use uuf6429\StateEngine\Implementation\Builder;

$turnstileStateMachine = Builder::create()
    // make states
    ->defState('locked', 'Impassable')
    ->defState('open', 'Passable')
    // make transitions
    ->defDataTransition('locked', ['insert_coin'], 'open', 'Coin placed')
    ->defDataTransition('open', ['walk_through'], 'locked', 'Person walks through')
    ->getMachine();

$turnstile = Turnstile::find(123);

// put coin in turnstile (notice that the final state is not mentioned)
$turnstileStateMachine->processInput($turnstile, ['insert_coin']);

// now $turnstile will be in "open" state