willdurand/propel-statemachine-behavior

该软件包已被废弃,不再维护。未建议替代软件包。
最新版本(1.0.0)的软件包没有可用的许可信息。

为您的模型添加有限状态机。

1.0.0 2014-01-30 08:33 UTC

This package is auto-updated.

Last update: 2022-01-31 11:58:52 UTC


README

Build Status

此行为为您的模型添加有限状态机。

配置

<behavior name="state_machine">
    <parameter name="states" value="draft, rejected, unpublished, published" />

    <parameter name="initial_state" value="draft" />

    <parameter name="transition" value="draft to published with publish" />
    <parameter name="transition" value="draft to rejected with reject" />
    <parameter name="transition" value="published to unpublished with unpublish" />
    <parameter name="transition" value="unpublished to published with publish" />

    <!-- Optional parameters -->
    <parameter name="state_column" value="state" />
    <parameter name="timestampable" value="true" />
</behavior>

state_machine 行为需要三个参数才能运行

  • states:以逗号分隔的状态有限集;
  • initial_state:初始状态,属于状态集合的一部分;
  • transition:一系列转换。如您所见,您可以添加任意多的 transition 参数。

每个转换都必须遵循以下模式

STATE_1 to STATE_2 with SYMBOL

一个 symbol(符号),作为有限状态机术语的一部分,可以被视为在您的模型对象上触发的事件。

可以添加 timestampable 选项以在 the_given_state_at 列中记录设置状态时的日期。

### ActiveRecord API ###

行为将生成以下常量,代表对象的可用状态

  • ObjectModel::STATE_DRAFT
  • ObjectModel::STATE_REJECTED
  • ObjectModel::STATE_UNPUBLISHED
  • ObjectModel::STATE_PUBLISHED

您可以获得对象当前的状态

getState()

或获取所有可用状态的数组

getAvailableStates()

通常,您希望显示这些状态。多亏了两个方便的方法,这变得非常简单

getHumanizedState() // 'Draft', or 'Rejected', or 'Unpublished', or 'Published'

ObjectModel::getHumanizedStates()
// array(
//  0 => 'Draft',
//  1 => 'Rejected',
//  2 => 'Unpublished',
//  3 => 'Published',
// )

行为还将生成一系列发行者

isDraft()

isRejected()

isPublished()

isUnpublished()

但最有趣的部分是实现有限状态机本身。首先,您有方法来根据当前模型的状态确定是否可以执行转换

canPublish()

canReject()

canUnpublish()

它还将为每个 symbol 生成一组方法

publish(PropelPDO $con = null)

unpublish(PropelPDO $con = null)

reject(PropelPDO $con = null)

为了处理自定义逻辑,创建了新的钩子。以下方法应返回布尔值,并可以作为 guards(不是有限状态机术语的一部分)。

prePublish(PropelPDO $con = null)

preUnpublish(PropelPDO $con = null)

preReject(PropelPDO $con = null)

以下方法应包含您自己的逻辑,取决于每个状态和您的业务。

onPublish(PropelPDO $con = null)

onUnpublish(PropelPDO $con = null)

onReject(PropelPDO $con = null)

以下方法允许在执行转换后执行代码。

postPublish(PropelPDO $con = null)

postUnpublish(PropelPDO $con = null)

postReject(PropelPDO $con = null)

ActiveQuery API

待定义。

用法

假设我们有一个 Post 模型类,它表示博客引擎中的一个条目。当我们创建一个新帖子时,其初始状态为 draft,因为我们不希望立即发布它。作为 draft,您可以决定发布您的新帖子。现在,其状态是 published。一旦 published,您可能出于某些原因想要取消发布。然后,其状态变为 unpublished。最后一种可能是重新发布一个 unpublished 帖子。新的状态是 published

我们有三种不同的状态(draftpublishedunpublished),以及三种转换

  • draftpublished
  • publishedunpublished
  • unpublishedpublished

我们可以定义以下配置

<table name="post">
    <!-- some columns -->

    <behavior name="state_machine">
        <parameter name="states" value="draft, unpublished, published" />

        <parameter name="initial_state" value="draft" />

        <parameter name="transition" value="draft to published with publish" />
        <parameter name="transition" value="published to unpublished with unpublish" />
        <parameter name="transition" value="unpublished to published with publish" />
    </behavior>
</table>

以下是一个工作流程

<?php

$post = new Post();

$post->getState();              // Post::STATE_DRAFT
$post->getAvailableStates();    // Post::STATE_DRAFT, Post::STATE_UNPUBLISHED, Post::STATE_PUBLISHED

$post->isDraft();               // true
$post->isPublished();           // false
$post->isUnpublished();         // false

$post->canPublish();            // true
$post->canUnpublish();          // false

$post->unpublish();             // throw a LogicException, no transition found from draft to unpublished

// Let's publish this post
// This is the first transition in the scenario above
$post->publish()->save();

$post->isDraft();               // false
$post->isPublished();           // true
$post->isUnpublished();         // false

$post->canPublish();            // false
$post->canUnpublish();          // true

$post->publish();               // throw a LogicException, the post is already published

// Let's unpublish this post
// This is the second transition in the scenario above
$post->unpublish()->save();

$post->isDraft();               // false
$post->isPublished();           // false
$post->isUnpublished();         // true

$post->canPublish();            // true
$post->canUnpublish();          // false

$post->unpublish();             // throw a LogicException, the post is already unpublished

// Let's (re)publish this post
// This is the last transition in the scenario above
$post->publish()->save();

$post->isDraft();               // false
$post->isPublished();           // true
$post->isUnpublished();         // false

$post->canPublish();            // false
$post->canUnpublish();          // true

现在想象一下,我们为每个帖子关联了作者,一旦帖子发布,我们就通过电子邮件通知帖子作者。多亏了新的钩子,扩展功能变得非常简单。

<?php

class Post extends BasePost
{
    // Assuming we have a mail manager which is able to send emails,
    // and that we injected it before.
    private $mailManager;

    public function onPublish(PropelPDO $con = null)
    {
        $this->mailManager->postPublished(
            $this->getAuthor(),
            $this->getTitle()
        );
    }
}

控制器中的用例

<?php

class PostController extends Controller
{
    public function newAction()
    {
        // handle a form, etc to create a new Post
    }

    public function publishAction(Post $post)
    {
        try {
            $post->publish()->save();
        } catch (\LogicException $e) {
            // handle the exception as you wish
        }
    }

    public function unpublishAction(Post $post)
    {
        try {
            $post->unpublish()->save();
        } catch (\LogicException $e) {
            // handle the exception as you wish
        }
    }
}

已知限制

  • 您不能使用deleted状态;
  • 您不能使用savedelete符号。

目前,没有内置的解决方案来处理这些情况。

组合存档行为

存档行为非常有用,可以将模型对象复制到存档表中。换句话说,它充当软删除行为,但性能更好。

在您的流程中,您可能出于某些原因想要销毁您的对象。我说“销毁”,因为您不能使用deleted状态,也不能使用delete符号,但这无关紧要。销毁对象是可以的,但与其硬删除,您可能希望软删除。这意味着您将依赖于存档行为。

只需将其添加到您的XML模式中,重建SQL和模型类,然后您就完成了。乍一看,当您destroy对象时,您会期望它被隐藏,但情况并非如此。它只是具有destroyed状态。

就像通常一样调用delete()终止方法,您的对象将被自动存档。不需要做太多。