willdurand / propel-statemachine-behavior
为您的模型添加有限状态机。
Requires
- php: >=5.2.4
- propel/propel1: ~1.6
Requires (Dev)
- behat/behat: ~2.2
- phpunit/phpunit: ~3.7
README
此行为为您的模型添加有限状态机。
配置
<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
。
我们有三种不同的状态(draft
、published
、unpublished
),以及三种转换
draft
到published
published
到unpublished
unpublished
到published
我们可以定义以下配置
<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
状态; - 您不能使用
save
或delete
符号。
目前,没有内置的解决方案来处理这些情况。
组合存档行为
存档行为非常有用,可以将模型对象复制到存档表中。换句话说,它充当软删除行为,但性能更好。
在您的流程中,您可能出于某些原因想要销毁您的对象。我说“销毁”,因为您不能使用deleted
状态,也不能使用delete
符号,但这无关紧要。销毁对象是可以的,但与其硬删除,您可能希望软删除。这意味着您将依赖于存档行为。
只需将其添加到您的XML模式中,重建SQL和模型类,然后您就完成了。乍一看,当您destroy
对象时,您会期望它被隐藏,但情况并非如此。它只是具有destroyed
状态。
就像通常一样调用delete()
终止方法,您的对象将被自动存档。不需要做太多。