noem/state-machine

一个基于事件的有限状态机,支持层次化和并行状态

dev-master 2024-03-30 20:20 UTC

This package is auto-updated.

Last update: 2024-08-30 21:20:31 UTC


README

CI

tmpdbpky87p

此库在PHP中提供了一个有限状态机(FSM)的实现。使用FSM架构的好处包括

  1. 简化系统行为建模:状态机有助于以结构化和易于理解的方式表示和组织系统的行为。
  2. 易于重构:可以不对业务逻辑产生影响,对系统架构进行重大更改。
  3. 可测试性:状态机允许更容易地测试单个状态和转换,从而更容易隔离和测试特定的系统行为。
  4. 降低复杂性:通过将复杂系统分解成更小、更易管理的状态,状态机可以简化整体系统设计并使其更容易理解。
  5. 可预测的行为:状态机确保系统以一致和可预测的方式运行,因为状态之间的转换是明确定义的。
  6. 文档:状态机充当文档形式,因为它们提供了系统行为和转换的视觉/文本表示。

功能

  • 嵌套区域:一个水平的状态集称为“区域”。然而,每个状态可以有任意数量的子区域,允许同时有并行状态层次化状态
  • 守卫:只有当谓词返回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();