james.rus52 / state-machine
一个非常轻量且强大的PHP状态机
0.6
2020-12-26 18:35 UTC
Requires
- php: ^7.1.3|^8.0
- symfony/event-dispatcher: ^4.3|^5.0
- symfony/expression-language: ^4.3|^5.0
- symfony/property-access: ^4.3|^5.0
Requires (Dev)
- phpunit/phpunit: ^8.5|^9.0
- twig/twig: ^3.0
Suggests
- twig/twig: Access the state machine in your twig templates (^3.0)
README
定义你的状态,定义你的转换和回调:我们做剩下的。硬编码状态的年代已经过去了!
安装(通过composer)
{ "require": { "james.rus52/state-machine": "~0.5" } }
使用
配置状态机图
为了使用状态机,您首先需要定义一个图。图是状态、转换以及可选的回调的定义;所有这些都与您的领域对象相关联。可以将多个图附加到同一对象上。
让我们为 DomainObject
对象定义一个名为 myGraphA 的图
$config = [ 'graph' => 'Request', 'property_path' => 'Status', 'states' => [ RequestStatus::NEW => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE, RequestAction::DELETE ], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ], RequestAction::DELETE => ['b_name' => 's_delete_request' ,'roles' => ['author'], 'onclick' => 'ConfirmDeleteRequest();' ] ] ], RequestStatus::ANALYZE => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE, RequestAction::ESCALATE, RequestAction::DELETE, RequestAction::SUSPEND, RequestAction::UNSUSPEND, ], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ], RequestAction::ESCALATE => ['b_name' => 's_escalation', 'onclick' => 'ShowModalWindow(\'owner_model_window\',\'SubmitForm\',\'owner_model_window_form\');' ], RequestAction::DELETE => ['b_name' => 's_delete_request' ,'roles' => ['superadmin'], 'onclick' => 'ConfirmDeleteRequest();' ], RequestAction::SUSPEND => ['b_name' => 's_suspend' ], RequestAction::UNSUSPEND => ['b_name' => 's_unsuspend'], ], 'conditions' => [ RequestAction::SUSPEND => ['object', 'isSuspended', false ], RequestAction::UNSUSPEND => ['object', 'isSuspended', true ], ] ], RequestStatus::APPROVE => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE, RequestAction::ESCALATE, RequestAction::DELETE, RequestAction::SUSPEND, RequestAction::UNSUSPEND, ], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ], RequestAction::ESCALATE => ['b_name' => 's_escalation', 'onclick' => 'ShowModalWindow(\'owner_model_window\',\'SubmitForm\',\'owner_model_window_form\');' ], RequestAction::DELETE => ['b_name' => 's_delete_request' ,'roles' => ['superadmin'], 'onclick' => 'ConfirmDeleteRequest();' ], RequestAction::SUSPEND => ['b_name' => 's_suspend' ], RequestAction::UNSUSPEND => ['b_name' => 's_unsuspend'], ], 'conditions' => [ RequestAction::SUSPEND => ['object', 'isSuspended', false ], RequestAction::UNSUSPEND => ['object', 'isSuspended', true ], ] ], RequestStatus::APPROVED => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE, RequestAction::ESCALATE, RequestAction::DELETE, RequestAction::SUSPEND, RequestAction::UNSUSPEND, ], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ], RequestAction::ESCALATE => ['b_name' => 's_escalation', 'onclick' => 'ShowModalWindow(\'owner_model_window\',\'SubmitForm\',\'owner_model_window_form\');' ], RequestAction::DELETE => ['b_name' => 's_delete_request' ,'roles' => ['superadmin'], 'onclick' => 'ConfirmDeleteRequest();' ], RequestAction::SUSPEND => ['b_name' => 's_suspend' ], RequestAction::UNSUSPEND => ['b_name' => 's_unsuspend'], ], 'conditions' => [ RequestAction::SUSPEND => ['object', 'isSuspended', false ], RequestAction::UNSUSPEND => ['object', 'isSuspended', true ], ] ], RequestStatus::SENT => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ] ] ], RequestStatus::DELIVERED => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE, RequestAction::ESCALATE, RequestAction::SUSPEND, RequestAction::UNSUSPEND, ], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ], RequestAction::ESCALATE => ['b_name' => 's_escalation', 'onclick' => 'ShowModalWindow(\'owner_model_window\',\'SubmitForm\',\'owner_model_window_form\');' ], RequestAction::SUSPEND => ['b_name' => 's_suspend' ], RequestAction::UNSUSPEND => ['b_name' => 's_unsuspend'], ], 'conditions' => [ RequestAction::SUSPEND => ['object', 'isSuspended', false ], RequestAction::UNSUSPEND => ['object', 'isSuspended', true ], ] ], RequestStatus::COMPLETED => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ] ] ], RequestStatus::CANCELED => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ] ] ], RequestStatus::REJECTED => [ 'actions' => [ RequestAction::COMMENT, RequestAction::CLONE], 'properties' => [ RequestAction::COMMENT => ['b_name' => 's_comment' ], RequestAction::CLONE => ['b_name' => 's_clone_request' ] ] ], ], 'transitions' => [ RequestTransition::CANCEL => [ 'from' => [RequestStatus::NEW], 'to' => RequestStatus::CANCELED, 'properties' => ['b_name' => 's_to_cancel', 'css_class' => 'btn-outline-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::TO_ANALYZE => [ 'from' => [RequestStatus::NEW], 'to' => RequestStatus::ANALYZE, 'properties' => ['b_name' => 's_to_analyze', 'css_class' => 'btn-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::REJECT => [ 'from' => [RequestStatus::ANALYZE], 'to' => RequestStatus::REJECTED, 'properties' => ['b_name' => 's_to_reject_admin', 'css_class' => 'btn-outline-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::BACK_TO_AUTHOR => [ 'from' => [RequestStatus::ANALYZE], 'to' => RequestStatus::NEW, 'properties' => ['b_name' => 's_return_to_author', 'css_class' => 'btn-outline-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::ANALYZE => [ 'from' => [RequestStatus::ANALYZE], 'to' => RequestStatus::APPROVE, 'properties' => ['b_name' => 's_aprove_my_resources', 'css_class' => 'btn-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::RETURN => [ 'from' => [RequestStatus::APPROVE], 'to' => RequestStatus::ANALYZE, 'properties' => ['b_name' => 's_to_returnanalyze', 'css_class' => 'btn-outline-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::REJECT => [ 'from' => [RequestStatus::APPROVE, RequestStatus::ANALYZE ], 'to' => RequestStatus::REJECTED, 'properties' => ['b_name' => 's_to_reject', 'css_class' => 'btn-outline-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::APPROVE => [ 'from' => [RequestStatus::APPROVE], 'to' => RequestStatus::APPROVED, 'properties' => ['b_name' => 's_to_approve', 'css_class' => 'btn-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::SEND => [ 'from' => [RequestStatus::APPROVED], 'to' => RequestStatus::SENT, 'properties' => ['b_name' => 's_to_partner', 'css_class' => 'btn-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::DELIVER => [ 'from' => [RequestStatus::SENT], 'to' => RequestStatus::DELIVERED, 'properties' => ['roles' => ['system']]], RequestTransition::RESEND => [ 'from' => [RequestStatus::DELIVERED], 'to' => RequestStatus::SENT, 'properties' => ['b_name' => 's_resend', 'css_class' => 'btn-primary', 'roles' => ['executor','superadmin']] ], RequestTransition::COMPLETE => [ 'from' => [RequestStatus::DELIVERED], 'to' => RequestStatus::COMPLETED, 'properties' => ['roles' => ['system']] ], RequestTransition::REOPEN => [ 'from' => [RequestStatus::COMPLETED, RequestStatus::REJECTED, RequestStatus::CANCELED], 'to' => RequestStatus::SENT, 'properties' => ['b_name' => 's_reopen', 'css_class' => 'btn-primary', 'roles' => ['author', 'executor','superadmin']] ], ], 'callbacks' => [ 'lock' => [ [ 'do' => ['object','getLock'], ], ], 'unlock' => [ [ 'do' => ['object','releaseLock'], ], ], 'before' => [ [ 'on' => RequestTransition::BACK_TO_AUTHOR, 'do' => ['object','BackToAuthor'], 'args' => [$this->params] ], [ 'on' => RequestTransition::CANCEL, 'do' => ['object','Cancel'], 'args' => [$this->params] ], [ 'on' => RequestTransition::TO_ANALYZE, 'do' => ['object','ToAnalyze'], 'args' => [$this->params] ], [ 'on' => RequestTransition::ANALYZE, 'do' => ['object','ToApprove'], 'args' => [$this->params] ], [ 'on' => RequestTransition::RETURN, 'do' => ['object','BackToAnalyze'], 'args' => [$this->params] ], [ 'on' => RequestTransition::REJECT, 'from' => RequestStatus::ANALYZE, 'do' => ['object','ToRejectByAdmin'], 'args' => [$this->params] ], [ 'on' => RequestTransition::REJECT, 'from' => RequestStatus::APPROVE, 'do' => ['object','ToReject'], 'args' => [$this->params] ], [ 'on' => RequestTransition::APPROVE, 'do' => ['object','ToComplete'], 'args' => [$this->params] ], [ 'on' => RequestTransition::SEND, 'do' => ['object','ToSendPartner'], 'args' => [$this->params] ], [ 'on' => RequestTransition::RESEND, 'do' => ['object','ToSendPartner'], 'args' => [$this->params] ], [ 'on' => RequestTransition::REOPEN, 'do' => ['object','Reopen'], 'args' => [$this->params] ], ], 'action' => [ [ 'action' => RequestAction::COMMENT, 'do' => ['object','AddComment'], 'args' => [$this->params['ta_comment'] ?? null] ], [ 'action' => RequestAction::CLONE, 'do' => ['object','CloneRequest'], ], [ 'action' => RequestAction::ESCALATE, 'on' => [RequestStatus::ANALYZE, RequestStatus::APPROVE, RequestStatus::APPROVED, RequestStatus::DELIVERED], 'do' => ['object','Escalate'], 'args' => [$this->params['s_escalation_owner'] ?? null] ], [ 'action' => RequestAction::DELETE, 'on' => [RequestStatus::NEW, RequestStatus::ANALYZE, RequestStatus::APPROVE, RequestStatus::APPROVED], 'do' => ['object','DeleteRequest'], ], [ 'action' => RequestAction::SUSPEND, 'do' => ['object','Suspend'], 'args' => [$this->params['ta_comment'] ?? null] ], [ 'action' => RequestAction::UNSUSPEND, 'do' => ['object','Unsuspend'], 'args' => [$this->params['ta_comment'] ?? null] ], ], 'guard' => [ [ 'on' => RequestTransition::RESEND, 'do' => ['object','hasResourcesError'], ] ] ] ];
因此,在上面的例子中,图有6种可能的状态,通过对该对象应用一些转换可以实现这些状态。例如,在创建一个新的 DomainObject
时,您会应用 'create' 转换到对象上,之后其状态将变为 pending。
使用状态机
定义
状态机是实际操作您的对象的那个对象。通过使用状态机,您可以测试转换是否可以应用,实际应用转换,检索当前状态等。状态机是特定于对象+图的。这意味着如果您想操作另一个对象,或者相同的对象使用另一个图,您需要另一个状态机。
工厂帮助您获取这些对象+图的组合状态机。您向它提供一个对象和一个图名称,它将返回这对组合的状态机。如果您想在Symfony2应用程序中将此工厂作为服务,请参阅相应的StateMachineBundle。
使用
请参阅 examples
文件夹中的几个示例。
回调
回调用于保护转换或在应用转换前后执行一些代码。
保护回调必须返回一个 bool
。如果一个保护返回 false
,则无法执行转换。
致谢
这个库受到了https://github.com/yohang/Finite的极大启发,但采取了不同的方向。
James的SM版本
从https://github.com/sebdesign/state-machine克隆
我添加了一些新功能
- 属性 - 用户定义的信息,您可以通过
- getTransitionProperties
- getStateProperties
- hasTransitionProperties
- hasStateProperties
- 锁定/解锁 - 您可以实现锁定对象,当有人对您尝试转换的文档执行转换时
- 动作 - 状态可以有一些动作,这些动作不执行转换也不锁定文档。
- 条件 - 这是状态动作的条件,类似于转换的保护