uuf6429 / state-engine
提供状态引擎或机器的接口和基本实现的库
2.1.0
2024-09-07 16:42 UTC
Requires
- php: ^7.4 || ^8.0
- psr/event-dispatcher: ^1.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.42
- friendsofphp/php-cs-fixer: ^3.53
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^9.5 || ^10
- roave/security-advisories: dev-latest
README
此库提供了一些接口和状态引擎或状态机的基实施
✨ 亮点:
- 双重功能
- 作为一个基本的状态引擎;只要定义了转换,就可以切换到所需状态(见 "JiraIssueTest")
- 更复杂的状态机;与上面相同,但匹配任何状态的数据(见 "TurnstileTest")
- 高度可组合 - 可以按需替换所有内容
- PSR-14(事件分发器)兼容
- 流畅的构建器接口(见 "From Scratch")
- 生成 Mermaid 或 PlantUML 标记(见 "示例 & 测试")
🔌 安装
安装此库的推荐且最简单的方法是通过 Composer
composer require "uuf6429/state-engine-php"
🧐 为什么?
原则上,这样的引擎易于实现,但在实践中,通常实现得很糟糕或者被遗忘。
例如,可能有一个 is_active
字段,认为不会存在其他状态,然后后来需要 is_pending
字段,这时重构标记到状态的标志就太晚了。
无论如何,此库抽象出这种情况,或者至少减少了代码量。
🤔 如何?
有几个关键部分说明了它是如何工作的
- 状态 - 表示模型单个状态的对象。因此,模型可能具有不同的状态级别,例如门可以有 打开 和 关闭 状态,但它也可以是 锁定 和 未锁定。在这种情况下,可以将门锁视为一个单独的模型(具有单独的引擎实例),或者合并所有状态:打开,关闭-未锁定 和 关闭-锁定。
- 转换 - 表示从一种状态到另一种状态的转换的对象。这就是您定义模型可以经过的各种状态流的方式。
- 转换存储库 - 意识到并提供所有可能允许的转换的对象。
- 引擎 - 执行模型从一个状态到另一个状态转换的对象。通常,您会在应用程序中的每个具有状态的模型上有一个引擎实例。
🚀 使用
您可以从头开始使用它,或者将其插入到现有的应用程序中。它基本上有三个部分
- 配置引擎(创建状态和转换)
- 使用引擎(例如,在 Web 控制器或服务中)
- (可选)处理事件(使用提供给引擎的相同事件分发器)
另一种情况是您需要提供一个有效转换的列表,例如提供给用户。在这种情况下,在存储库上使用 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