noem / state-machine
一个基于事件的有限状态机,支持层次化和并行状态
dev-master
2024-03-30 20:20 UTC
Requires
- php: ^8.1
- nette/schema: ^1.3
- psr/container: ^2.0
- symfony/yaml: ^5.4
Requires (Dev)
- mockery/mockery: ^1.4
- noem/composer-file-embed: dev-master
- phpunit/phpunit: ^9.5
- spatie/phpunit-watcher: ^1.23
- squizlabs/php_codesniffer: ^3.6
This package is auto-updated.
Last update: 2024-08-30 21:20:31 UTC
README
此库在PHP中提供了一个有限状态机(FSM)的实现。使用FSM架构的好处包括
- 简化系统行为建模:状态机有助于以结构化和易于理解的方式表示和组织系统的行为。
- 易于重构:可以不对业务逻辑产生影响,对系统架构进行重大更改。
- 可测试性:状态机允许更容易地测试单个状态和转换,从而更容易隔离和测试特定的系统行为。
- 降低复杂性:通过将复杂系统分解成更小、更易管理的状态,状态机可以简化整体系统设计并使其更容易理解。
- 可预测的行为:状态机确保系统以一致和可预测的方式运行,因为状态之间的转换是明确定义的。
- 文档:状态机充当文档形式,因为它们提供了系统行为和转换的视觉/文本表示。
功能
- 嵌套区域:一个水平的状态集称为“区域”。然而,每个状态可以有任意数量的子区域,允许同时有并行状态和层次化状态。
- 守卫:只有当谓词返回
true
时,才启用给定的转换。 - 操作:向机器调度操作以实现有状态的行为。只有与活动状态相对应的操作处理器会被调用。
- 入口 & 出口事件:将任意订阅者附加到状态更改。
- 区域 & 状态上下文:存储与当前应用程序状态相关的数据。数据可以针对单个状态进行范围限制,或者与整个区域共享。
- 状态继承:由于区域可以嵌套,每个区域都可以请求从父区域传递下来的特定数据。
- 中间件:在创建最终机器之前,您可以增强您的定义以使用可重用的中间件。
安装
使用composer安装此包
composer require noem/state-machine
用法
使用RegionBuilder
Noem状态机中的RegionBuilder
是一个用于构建和配置有限状态机的类。它允许开发者在状态机内部定义状态、转换、守卫、入口和出口事件以及操作,这使得在应用程序中实现有状态的行为变得方便。
<?php declare(strict_types=1); use Noem\State\RegionBuilder; $r = (new RegionBuilder()) // Define all possible states ->setStates('off', 'starting', 'on', 'error') // if not called, will default to the first entry ->markInitial('off') // if not called, will default to the last entry ->markFinal('error') // Define a transition from one state to another // <FROM> <TO> <PREDICATE> ->pushTransition('off', 'starting', fn(object $trigger):bool => true) // no predicate means always true ->pushTransition('starting', 'on') ->pushTransition('on', 'error', function(\Throwable $exception){ echo 'Error: '. $exception->getMessage(); return true; }) // Add a callback that runs whenever the specified state is entered ->onEnter('starting', function(object $trigger){ echo 'Starting application'; }) ->onAction('on',function (object $trigger){ // TODO: Main business logic echo $trigger->message; }) ->build(); // returns the actual Region object while(!$r->isFinal()){ $r->trigger((object)['message'=>'hello world']); }
使用RegionLoader
您还可以从YAML加载状态机配置。RegionLoader::fromYaml()
将提供一个RegionBuilder
,您可以对它进行进一步修改或立即开始使用。以下是一个示例
states: - name: one transitions: - target: two - name: two regions: states: - name: one_one transitions: - target: one_two - name: one_two transitions: - target: one_three - name: one_three transitions: - target: three - name: three initial: one final: three
此配置可以按以下方式加载
<?php declare(strict_types=1); use Noem\State\RegionLoader; $yaml = file_get_contents('./path/to/machine.yaml'); $builder = (new RegionLoader())->fromYaml($yaml); $builder->pushMiddleware(/** more on that in the next chapter */)->build();
中间件
很容易想到可以从一个机器带到另一个机器的常见且重复的问题,例如
- 日志记录:通过在每次入口/出口事件上添加监听器来跟踪任何状态更改。
- 异常处理:添加一个错误状态以及当捕获到异常时转换到该状态的转换。
- 保存/恢复状态:序列化机器上下文,并在重新初始化时恢复。
对于此场景,RegionBuilder
提供了对中间件的支撑,这些中间件可以在机器构建之前对机器进行任意修改。
此示例展示了简单的日志记录中间件
<?php declare(strict_types=1); use Noem\State\RegionBuilder; $logs = []; $middleware = function (RegionBuilder $builder, \Closure $next) use (&$logs) { $builder->eachState(function (string $s) use ($builder, &$logs) { $builder->onEnter($s, function (object $trigger) use (&$logs) { $logs[] = "ENTER: $this"; }); $builder->onExit($s, function (object $trigger) use (&$logs) { $logs[] = "EXIT: $this"; }); }); return $next($builder); }; $region = (new RegionBuilder()) ->setStates('foo', 'bar') ->pushTransition('foo', 'bar') ->pushMiddleware($middleware) ->build();