star/state-machine

PHP 实现的可配置状态机、工作流和领域模型的转换

3.0.0 2022-10-16 17:18 UTC

This package is auto-updated.

Last update: 2024-09-16 21:26:16 UTC


README

Build Status Scrutinizer Code Quality Code Coverage

此包可以帮助您为特定上下文构建工作流,该工作流可以封装在给定的上下文中。

它被设计为避免对包有硬依赖。该库不需要您实现任何方法。您需要编写的所有代码都可以封装在您的上下文类中,并且对其他对象保持隐藏。

安装

composer require star/state-machine

特性

状态

状态只是上下文可能处于的名称。状态通常保存在持久化平台中,或者使用一个 字符串 表示或一个 StateMetadata 类来保存在模型中。

转换

转换是对上下文的一个操作,可以将上下文从一个状态移动到另一个状态。转换只能有一个目标状态,因为没有方法让机器知道要移动到哪个状态。另一方面,转换可能有多个起始状态。

如果没有转换包含上下文的当前状态作为起始状态,将引发异常(除非提供了另一个 TransitionCallback)。

属性

属性用于标记状态对上下文有含义。

例如,如果您需要将状态视为活动状态或已关闭状态,而另一个状态不应如此,您只需将 is_activeis_closed 属性添加到需要它们的那些状态中即可。

// your context using the builder
public function isActive()
{
    return $this->stateMachine()-hasAttribute("is_active");
}
// your context using the metadata
public function isActive()
{
    return $this->state->hasAttribute("is_active");
}

使用示例

假设您有一个 Post 上下文,它可以有以下状态

  • 草案:该文章仅对创建者和版主可见
  • 已发布:该文章对所有用户可见
  • 存档:该文章仅对创建者可见

该文章允许的工作流如下

您可以将 Post 类定义为以下模式之一。

在模型中使用构建器

class Post
{
    /**
     * @var string
     */
    private $state;

    public function publish()
    {
        $this->state = $this->stateMachine()->transit("publish", $this);
    }

    public function archive()
    {
        $this->state = $this->stateMachine()->transit("archive", $this);
    }

    public function unarchive()
    {
        $this->state = $this->stateMachine()->transit("unarchive", $this);
    }

    public function isClosed()
    {
        return $this->stateMachine()->hasAttribute("is_closed");
    }

    /**
     * @return StateMachine
     */
    private function stateMachine()
    {
        return StateBuilder::build()
            ->allowTransition("publish", "draft", "published")
            ->allowTransition("archive", "published", "archived")
            ->allowTransition("unarchive", "published", "draft")
            ->addAttribute("is_closed", ["archived", "drafted"])
            ->create($this->state);
    }
}

将工作流封装在类中

如果您有多个模型可以具有相同的工作流,可以使用 StateMetadata 定义一个封装工作流的类。

final class MyStateWorkflow extends StateMetadata
{
    protected function __construct()
    {
        parent::__construct('pending');
    }

    protected function createMachine(StateBuilder $builder)
    {
        $builder->allowTransition("publish", "draft", "published")
        $builder->allowTransition("archive", "published", "archived")
        $builder->allowTransition("unarchive", "published", "draft")
        $builder->addAttribute("is_closed", ["archived", "drafted"])
    }
}

class Post
{
    /**
     * @var string
     */
    private $state;

    public function __construct()
    {
        $this->>state = new MyStateWorkflow();
    }

    public function publish()
    {
        $this->state = $this->state->transit("publish", $this);
    }

    public function archive()
    {
        $this->state = $this->state->transit("archive", $this);
    }

    public function unarchive()
    {
        $this->state = $this->state->transit("unarchive", $this);
    }

    public function isClosed()
    {
        return $this->state->hasAttribute("is_closed");
    }
}

状态的持久化

该包支持以下持久化引擎

事件

状态机有一个内部事件处理系统。

在多个位置触发多个事件,这使得您可以在某些转换上挂钩系统以添加行为。

监听这些事件的订阅者将对其配置的回调进行调用。

  • StateEventStore::BEFORE_TRANSITION:在上下文的任何转换之前执行事件。请参阅 TransitionWasRequested
  • StateEventStore::AFTER_TRANSITION:该事件在任何转换在上下文中执行后执行。参见 TransitionWasSuccessful
  • StateEventStore::FAILURE_TRANSITION:该事件在转换异常触发之前执行。参见 TransitionWasFailed

在机器中订阅监听器

$stateMachine->addListener(
    StateEventStore::BEFORE_TRANSITION,
    function(TransitionWasRequested $event) {
        // do something
    }
);

转换回调

在请求转换时,另一种将钩子连接到过程的方式是传递一个 转换回调

转换回调允许在转换之前、之后或转换不被允许时执行一个动作。默认情况下,将触发一个异常。参见 AlwaysThrowExceptionOnFailure

转换上的回调

$this->state->transit("transition", $this, new DoSomethingOnSuccessIfConditionMatches());