在Zend Framework3 + Doctine2上实现状态机

简介:为什么我们需要状态机


在应用程序中,通常有必要限制对对象某些操作的访问。 为此,使用RBAC模块来解决根据用户权限限制访问的问题。 根据对象的状态来管理动作的任务尚未解决。 使用状态机或状态机可以很好地解决此问题。 方便的状态机使您不仅可以在一个位置收集对象状态之间的所有转换规则,还可以通过分离转换规则,检查条件和处理并使它们服从通用规则来对代码进行一些排序。


我想分享使用教义2的Zend Framework 3状态机的实现
使用数据库。 该项目本身可以在这里找到。


在这里,我想分享一些基本原则。


让我们开始吧




我们将过渡图的描述存储在数据库表中,因为:


  1. 这很清楚。
  2. 允许您使用与感兴趣的字典相同的状态字典
    我们有状态的物体。
  3. 允许您使用外键来保证数据库的完整性。

使用非确定性有限状态机将增加我们解决方案的灵活性。


将使用通过一对多关系连接的一对表A和B来描述过渡图。


表一:


CREATE TABLE `tr_a` ( `id` int(11) NOT NULL AUTO_INCREMENT, `src_id` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `action_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT ' ', `condition` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '   ', PRIMARY KEY (`id`), KEY `IDX_96B84B3BFF529AC` (`src_id`), KEY `IDX_96B84B3B9D32F035` (`action_id`), CONSTRAINT `FK_96B84B3B9D32F035` FOREIGN KEY (`action_id`) REFERENCES `action` (`id`), CONSTRAINT `FK_96B84B3BFF529AC` FOREIGN KEY (`src_id`) REFERENCES `state` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

表B:


 CREATE TABLE `tr_b` ( `id` int(11) NOT NULL AUTO_INCREMENT, `transition_a_id` int(11) NOT NULL, `dst_id` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `weight` int(11) DEFAULT NULL COMMENT '  ,- , null-  ', `condition` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '   ', `pre_functor` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' ,  ,   ', `post_functor` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' ,  ,   ', PRIMARY KEY (`id`), KEY `IDX_E12699CB85F4C374` (`transition_a_id`), KEY `IDX_E12699CBE1885D19` (`dst_id`), CONSTRAINT `FK_E12699CB85F4C374` FOREIGN KEY (`transition_a_id`) REFERENCES `tr_a` (`id`), CONSTRAINT `FK_E12699CBE1885D19` FOREIGN KEY (`dst_id`) REFERENCES `state` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

我将省略实体类的定义;您可以在此处看到一个示例:


  1. 表一
  2. 表B

这里的一切都是标准的,包括对实体之间关系的描述。


要使用状态机,我们只需要一些公共方法


 /** *            * @param object $objE * @param string $action * @param array $data extra data * @return array * @throws ExceptionNS\StateMachineException */ public function doAction($objE, $action, array $data = []) /** *          * @param object $objE * @param string $action * @param array $data * @return bool */ public function hasAction($objE, $action, $data=[]) 

还有更多公共方法可以方便使用,但在这里我要注意主要的doAction()方法的算法。


从对象中,我们获得其状态。


知道它和动作标识符,实体-A可以轻松地在转换表A中找到。
由条件标识符获得的条件(位于来自实体A的condition使您可以检查操作是否可以执行。 特别是,开头提到的RBAC子句可以在条件验证器中使用。


验证者将通过ValidatorPluginManagercondition字段中的标识符中找到,并且
应该实现\Zend\Validator\ValidatorInterface 。 我更喜欢使用ValidatorChain的继承人。 这使得更改受控条件的组成变得容易,并且可以将简单的验证器重新用作测试链的一部分。


我们确定了过渡,检查了条件。 由于我们有不确定性
状态机 ,操作的结果可能是几种状态之一。


这种情况不是很常见,但是拟议的项目易于实施。
通过连接A-<B,我们获得了对象(实体B)的可能新状态的集合。
要选择单个状态,我们依次从结果集合中检查Entity-B的条件,并在weight字段中按weight从大到小对它们进行排序。 对条件的第一次成功检查为我们提供了Entity-B,其中有对象的新状态(请参见dst_id字段)。


确定新状态。 现在,在更改状态之前,状态机将执行
在prefunctor中定义的操作,然后它将更改状态并执行操作,
在后功能中定义。 状态机将使用插件管理器基于pre函数的pre_functor字段和post函数的pre_functor字段获取函子,并将为接收到的对象调用__invoke()方法。


不必使用函子来更改状态。 这是状态机的任务。 如果在更改状态时无需执行其他操作,则将上述字段设置为null。


其他芯片:


  1. 在转换表condition的字段中, pre_funtorpost_functor我使用别名,我认为这很方便。
  2. 为了方便视觉,请从表A和B创建视图。
  3. 我将字符串标识符用作状态和操作词典中的主键。 这不是必需的,但很方便。 也可以使用带有数字标识符的字典。
  4. 由于使用了不确定的有限状态机,因此该动作不必导致状态改变。 例如,这使您可以描述诸如查看之类的操作。
  5. 除了用于验证动作和执行动作的方法外,还有许多公共方法可以允许例如在考虑检查的情况下获得针对对象给定状态的动作列表或针对对象给定状态的可用动作列表。 通常在网格中的每个记录的界面中,您需要显示一组操作。 这些状态机方法将有助于获得必要的列表。
  6. 当然,可以在函子内部调用其他状态机,此外,您可以调用自己,但使用不同的对象或使用相同的对象,但是要在状态改变后(即在后函数器中)。 这有时对于在客户“突然”改变的情况下组织级联转换很有用;)

结论


尽管有许多任务非常适合使用状态机,但Web程序员相对很少使用它们。 我看到的同样的决定对我来说似乎很可怕。


我希望所提出的解决方案将帮助某人节省实施时间。

Source: https://habr.com/ru/post/zh-CN413701/


All Articles