mothership / state_machine
基于PHP的状态机实现,完全通过外部.yml文件配置,并渲染图形描述
Requires
- php: >=5.4
- symfony/console: ~2.7
- symfony/yaml: ~2.7
Requires (Dev)
README
一个基于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
用法
状态机需要三个文件才能运行
- 一个具体的州机
- yaml配置文件
- 工作流定义
快速入门
如果您拥有所有内容或只想跳过更详细的部分,请查看此存储库中的此目录
/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
字段的名称。如果有条件,转换名称也会更改。 - 您不需要为状态转换实现任何编程逻辑。
- 状态的可能类型可以是initial、normal或final。
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_gallery
和 create_media_gallery
|get_images
如您所见,您需要在 开始 状态之后有一个条件。这意味着状态 product_has_media_gallery
需要返回布尔值 true 或 false。为了能够这样做,您需要像这样更新您的 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_images
、has_more
和 finish
。
如果有更多内容要处理,则继续到 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
以探索更多高级示例。您还可以将新的状态机添加到此目录,以自动运行单元测试。
简单
SimpleLoop
如果条件
布尔条件
高级工作流程
异常工作流程
此工作流相当高级,它不是一个标准的自动机,因为DFA只有一个定义的起始点。如果你需要构建更复杂的状态机,其中可能在不同部分的代码中有异常,并且不知道在哪里处理它,那么它更有用。可以将异常处理建模为每个节点的转换,但副作用是在较大的图中会有很多转换。
不建议使用这样的通用异常处理,特别是如果你不知道你的工作流程在做什么。
测试
- 从根目录运行测试:
phpunit --coverage-text
- 您可以将您的状态机添加到
./src/Examples
文件夹中,它将使用上述命令自动测试。只需使用相同的命名空间约定
注意
- 母船状态机 受到这个扩展中存在的 Finite/StateMachine 的启发
- dev-master api