mothership/state_machine

基于PHP的状态机实现,完全通过外部.yml文件配置,并渲染图形描述

v1.4.5 2017-09-12 16:42 UTC

README

Summary Latest Stable Version Total Downloads Latest Unstable Version License

一个基于PHP的状态机实现,带有图形生成器。

更多信息请访问网站

设计目标

状态机(FSM)有很多实现。但大多数实现没有适当的文档,或者在某些方面过度工程化。这个库试图通过阅读代码成为您自己实现的起点。实际上,您也可以将其用于自己的项目。

功能

  • 创建一个FSM兼容的状态机
  • 完全在配置文件中定义您的状态、转换和条件。目前仅支持YAML
  • 不需要额外的逻辑来处理转换。状态机将自动尝试检测有效转换
  • 渲染一个图形图像,显示状态机的行为。

测试

您可以使用以下命令运行单元测试

$ cd path/to/Mothership/Component/StateMachine/ #check your path
$ composer install
$ phpunit

安装

您可以使用composer直接在您的项目中安装。请勿使用master分支,而仅使用标记版本。

"require": {
	"mothership/state_machine": "v1.3.*"
}

然后只需运行composer install

$ composer install

渲染图形的依赖

状态机渲染图形功能依赖于graphviz库。在apt支持的操作系统上,只需使用包管理器安装它。

sudo apt-get install graphviz

用法

状态机需要三个文件才能运行

  1. 一个具体的州机
  2. yaml配置文件
  3. 工作流定义

快速入门

如果您拥有所有内容或只想跳过更详细的部分,请查看此存储库中的此目录

/src/Examples/Simple/SimpleStateMachine.php
$stateMachine = new \Mothership\StateMachine\Examples\Simple('workflow.yml');
$stateMachine->run();

1. 具体的状态机

此库中没有具体的StateMachine实现,除了示例。然而,实现非常简单,实际上只是抽象类的继承。

// set your own namespace
namespace Mothership\StateMachine\Examples\Simple;

class SimpleStateMachine extends \Mothership\StateMachine\StateMachineAbstract
{
// that's all!
}

2. yaml配置文件

yaml配置文件是文件中最重要的,因为它描述了各种业务场景。您将在稍后找到更高级的使用案例。最初,请查看以下工作流程。

顺便说一句:图形已经使用graphviz渲染。您也可以轻松地使用以下命令自己渲染它

$stateMachine->renderGraph('/your/path/file.png');

如果您熟悉状态机的概念,您将知道状态机包含

  • 一个定义的起始点
  • 一个定义的终点
  • 一定数量的转换
  • 一组各种状态

让我们看看,如何通过检查workflow.yml来查看配置文件可能的样子

class:
  name: Mothership\StateMachine\Examples\Simple\SimpleWorkflow
  args: []

states:
  start:
    type: initial
  second_state:
    type: normal
    transitions_from: 	[start]
    transitions_to: 	[second_state]
  third_state:
    type: normal
    transitions_from: 	[second_state]
    transitions_to: 	[third_state]
  finish:
    type: final
    transitions_from:  [third_state]
    transitions_to:    [finish]

值得关注

  • 除了start以外的每个状态都需要作为一个方法实现。例如,名为second_state的状态期望在workflow类中有一个名为second_state()的方法。
  • 转换的名称始终等于transitions_to字段的名称。如果有条件,转换名称也会更改。
  • 您不需要为状态转换实现任何编程逻辑。
  • 状态的可能类型可以是initialnormalfinal

3. 工作流类

虽然配置文件定义了自动机的可能转换,但工作流类包含可能的转换。让我们检查以下代码

namespace Mothership\StateMachine\Examples\Simple;

use Mothership\StateMachine\WorkflowAbstract;

class SimpleWorkflow extends WorkflowAbstract
{

    function second_state()
    {

    }

    function third_state()
    {

    }

    function finish()
    {
    }
}
  • 没有 start() 方法,因为这个状态永远不会被执行,只是一个起点。
  • 基本方法不返回任何值。更高级的方法可以例如返回布尔值或任何字符串。这在更高级的使用场景中很重要。
  • 类的名称 必须 与配置文件中的类名称匹配。
  • 在每个状态之后,您可以使用 preDispatch()postDispatch() 方法。使用回调处理程序可能更好,但为了保持库的大小适中、易于理解并适用于基本案例,做出了这个决定。

渲染图表

建议将您的流程以图表的形式渲染,以便您将获得视觉支持。

$state_machine = new StateMachine();
$state_machine = $state_machine->renderGraph($path, false);

$paht:状态机保存图像的路径

是/否:如果您希望在渲染后退出状态机(默认为是)

高级用例

让我们假设一个更高级的工作流程,如下所示:[高级工作流程](https://github.com/mothership-gmbh/state_machine/blob/HEAD/./src/Examples/Advanced/Workflow.yml)。渲染的图表将如下所示

我们将通过更小的例子来讨论不同的转换类型。

条件

检查转换 product_has_media_gallerycreate_media_gallery|get_images

如您所见,您需要在 开始 状态之后有一个条件。这意味着状态 product_has_media_gallery 需要返回布尔值 truefalse。为了能够这样做,您需要像这样更新您的 yaml 配置

  product_has_media_gallery:
    type: normal
    transitions_from: [download_directory_exist]
    transitions_to:   [product_has_media_gallery]

  create_media_gallery:
    type: normal
    transitions_from: [{status: product_has_media_gallery, result:  false}]
    transitions_to:   [create_media_gallery]

  get_images:
    type: normal
    transitions_from: [{status: product_has_media_gallery, result:  true}, create_media_gallery]
    transitions_to:   [get_images]

  • 条件始终设置在目标转换中

  • 即使我们在这里使用布尔值,您也可以返回一个字符串!这也是有效的

     transitions_from: [{status: product_has_media_gallery, result:  'yes, okay'}]
    
  • 状态 get_images 可以使用两种不同的转换来处理。

循环

循环在需要处理大量数据时非常有用。模式非常简单。看看状态 process_imageshas_morefinish

如果有更多内容要处理,则继续到 process_images,否则转到 finish。作为一个矩阵,这将是

只需这样做

  process_images:
    type: normal
    transitions_from: [get_images, {status: has_more, result:  true}]
    transitions_to:   [process_images]
    
  has_more:
    type: normal
    transitions_from: [assign_image]
    transitions_to:   [has_more]

  finish:
    type: final
    transitions_from:  [{status: has_more, result:  false}]
    transitions_to:  [finish]

异常处理

您可以在不同情况下使用异常处理。

定义一个异常状态

这并不是一个典型的 FSA。但定义外部异常可以帮助您构建更具容错性的状态机。然而,您应该非常小心地使用它。定义异常状态可能会导致一些不良的设计决策,因为您将异常处理的责任留给了适当的异常状态。

仔细考虑异常状态的使用

  • 我需要通用异常处理吗?如果您需要一个集中的异常处理,这可能是有用的,但您也可以在每个状态中构建 try-catch 块。
  • 我需要重新运行失败的转换吗?您可以使用异常状态从定义的转换重新运行状态机。只需在 yaml 文件中设置转换即可。

捕获运行方法

将运行方法包装在 try-catch 中并构建自定义异常处理器。以集中方式使用它很有用。但是,很难再次返回到工作状态。

  public function run(array $args = [])
  {
    try {
        parent::run($args);
    } catch (WorkflowException $e) {
        $this->handleException('Workflow', $e);
    } catch (StatusException $e) {
        $this->handleException('Workflow', $e);
    } catch (\Exception $e) {
        $this->handleException('General', $e);
    }
  }

在状态中捕获

捕获任何您想要控制的异常。建议尽可能具体。

test_state()
{
    try {
    } catch (\Highest\Exception $e) {
        echo $e->getMessage();
    }
}

更多示例

检查目录 ./src/Examples 以探索更多高级示例。您还可以将新的状态机添加到此目录,以自动运行单元测试。

简单

Advanced Workflow

SimpleLoop

Advanced Workflow

如果条件

Advanced Workflow

布尔条件

Advanced Workflow

高级工作流程

Advanced Workflow

异常工作流程

此工作流相当高级,它不是一个标准的自动机,因为DFA只有一个定义的起始点。如果你需要构建更复杂的状态机,其中可能在不同部分的代码中有异常,并且不知道在哪里处理它,那么它更有用。可以将异常处理建模为每个节点的转换,但副作用是在较大的图中会有很多转换。

不建议使用这样的通用异常处理,特别是如果你不知道你的工作流程在做什么。

Exception Workflow

测试

  • 从根目录运行测试: phpunit --coverage-text
  • 您可以将您的状态机添加到 ./src/Examples 文件夹中,它将使用上述命令自动测试。只需使用相同的命名空间约定

注意