littlerocket / workflow-extensions-bundle
扩展工作流管理和自动化的包
Requires
- php: ^7.4 || ^8.0
- psr/container: *
- symfony/expression-language: *
- symfony/framework-bundle: ^4.4 || ^5.4
- symfony/workflow: *
Requires (Dev)
Suggests
- doctrine/doctrine-bundle: Required for OrmPersistentMarkingStore usage
- littlerocket/job-queue-bundle: Needed for workflow transitions scheduling
- nesbot/carbon: Needed for workflow transitions scheduling
- symfony/console: Needed for workflow transitions scheduling
README
Original Symfony 3 Workflow component and the part of Symfony's FrameworkBundle that integrates it in Symfony's ecosystem state explicit declaring all the things as a main concept. This means that if you need to apply workflow transition you must find target workflow object and call $workflow->apply()
inside your business logic code. Another example: if you want to block transition you must create a listener that subscribes to GuardEvent and decide whether to block or allow transition inside.
But sometimes workflow processes are complex and the code handles transition management grows quickly.
WorkflowExtensionsBundle provides extensions and additional features to original Symfony 3 Workflow component that can help you to automate some actions you must do manually when you deal with workflow component out of the box
需求
由于 Symfony 的 Workflow 组件需要 PHP 5.5.9+,因此 WorkflowExtensionsBundle 支持 PHP 5.5.9 及更高版本。
从 3.2 版本开始,Workflow 组件集成在 Symfony 3 生态系统中。要在基于 Symfony 3.1 及以下版本的应用中使用它,您可以使用 1.x 版本的 Bundle。
除了 symfony/framework-bundle 和 symfony/expression-language 包外,还需要其他一些包。
安装
应通过 composer 安装此包
composer require gtt/workflow-extensions-bundle
之后,您需要将包注册到应用程序内核中
public function registerBundles() { $bundles = array( // ... new \Gtt\Bundle\WorkflowExtensionsBundle\WorkflowExtensionsBundle(), ); }
配置和使用
工作流主题
首先,您需要告诉 WorkflowExtensionsBundle 它需要处理哪种类型的工作流主题。在 subject_manipulator
部分中列出。
workflow_extensions: subject_manipulator: My\Bundle\Entity\Order: ~ My\Bundle\Entity\Claim: ~
日志记录
由于 WorkflowExtensionsBundle 所做的一切基本上都是自动化的(甚至异步),因此合理地详细记录重要方面是合理的。所有 WorkflowExtensionsBundle 子系统在执行过程中(当可能时)记录工作流名称、主题类和主题 ID。
这里有一个非平凡的问题:如何从主题中检索主题 ID。更常见的是,可以通过调用 getId()
方法来获取主题 ID,在这种情况下,您不需要做任何事情。否则(当您的主题类没有 getId()
方法或应使用其他方法来获取主题的标识符时),您需要指定一个表达式来获取主题标识符。这个表达式将由 ExpressionLanguage 组件使用 subject
变量来评估,该变量代表主题对象
workflow_extensions: subject_manipulator: My\Bundle\Entity\Order: ~ My\Bundle\Entity\Claim: id_from_subject: 'subject.getCustomId()'
基于事件的转换处理
WorkflowExtensionsBundle最重要的用例之一是响应特定系统事件执行一些工作流程操作。任何Symfony的事件实例都可以扮演这种触发事件的角色。为了将工作流程处理订阅到此类事件,您应该从如下配置开始
workflow_extensions: workflows: simple: triggers: event: some.event: ... another.event: ... complex: triggers: event: some.event: ... third.event: ... ...
此配置首先指定目标工作流程名称(simple
),它应该等于symfony/framework-bundle或fduch/workflow-bundle配置中定义的工作流程之一。对于每个工作流程,然后您定义目标事件并配置以下各节中描述的处理详情。
基于事件的转换触发
WorkflowExtensionsBundle使得在特定事件被触发时触发工作流程转换成为可能。例如,如果您想在工作流程主题(My\Bundle\Entity\Order实例)创建时触发转换to_processing
(order_created.event被触发),则WorkflowExtensionsBundle的配置可以如下所示
workflow_extensions: workflows: simple: triggers: event: order_created.event: actions: apply_transition: - [to_processing] subject_retrieving_expression: 'event.getOrder()' subject_manipulator: My\Bundle\Entity\Order: ~
在上面的示例中,subject_retrieving_expression
部分包含用于检索工作流程主题的表达式(它将由ExpressionLanguage评估)。由于评估这些表达式的表达式语言启用了容器变量(表示DI容器),因此您可以构建更复杂的东西,例如以下内容:"container.get('doctrine').getEntityManagerForClass('My\\\\Bundle\\\\Entity\\\\Order').find('My\\\\Bundle\\\\Entity\\\\Order', event.getId())"
(由于表达式语言语法的特殊性,设置了大量的反斜杠)。
您还可以通过使用apply_transitions
构造来指定在事件被触发时要尝试执行多个转换,如下所示
apply_transitions: - [[to_processing, closing]]
在这种情况下(默认情况下),将应用第一个适用的转换。您还可以连续尝试应用多个转换,而不会在第一个成功应用的转换后中断执行,这可以通过在行中多次调用apply_transition
动作并使用不同的参数来实现。
apply_transition: - [to_processing] - [closing]
基于事件的转换调度
事件不仅可以用来立即应用转换,还可以使用指定的偏移量进行调度。WorkflowExtensionsBundle使用jms/job-queue-bundle作为调度引擎。假设您需要应用转换set_problematic
,该转换将工作流程主题Order
放置到“Problematic”状态,如果它没有在30天内正确处理。可以使用如下配置实现此目标
workflow_extensions: workflows: simple: triggers: event: order_created.event: schedule: apply_transition: - arguments: [closing] offset: P30D subject_retrieving_expression: 'event.getOrder()' scheduler: ~ subject_manipulator: My\Bundle\Entity\Order: subject_from_domain: "container.get('doctrine').getManagerForClass(subjectClass).find(subjectClass, subjectId)" context: doctrine: ~
上述配置与之前的配置类似,但有几个区别。
第一个区别是,将actions
替换为schedule
键,以告诉引擎以下操作应被延迟执行。
第二个区别是,每个动作的参数现在定义在显式的arguments
键下(由于配置规范化规则,该键会自动设置,并且也可以显式设置),以及定义时间间隔的offset
键(根据ISO-8601),从相应的触发事件发生时开始,然后在此之后应用计划中的转换。
第三个区别是,您需要配置scheduler
部分以激活调度引擎。您还可以使用它来设置特定的实体管理器以持久化调度作业。
第四个不同之处在于,您必须在 subject_manipulator
的 subject_from_domain
键表达式(它将通过 ExpressionLanguage 评估)下进行配置,该表达式将用于在尝试应用计划转换时检索工作流主题。subjectClass(例如 My\Bundle\Entity\Order)和subjectId(即您可以用来获取对象的标识符)是这里的表达式变量。此外,您还可以在这里再次使用 DI 容器,因为它也注册为表达式变量。
这里的另一个特性是,如果您有一个经常重复的事件来安排转换,那么当事件第一次被触发时,转换将被简单地安排,并且下一次事件发生将只是重置计时器,从当前时刻重新启动。当您需要连续延迟特定转换,直到特定事件定期触发时,这种行为非常有用。您无需配置任何特定内容来实现此功能,因为这个功能默认启用。
转换阻塞
基本上,您可以通过监听特殊的 GuardEvent 并在其内部调用 setBlocked
方法来显式地阻止转换的应用。借助 WorkflowExtensionsBundle,您可以再次自动化一些事情。例如,如果您要阻止所有由非-ROLE_USER 用户调用的转换,并仅允许管理者(ROLE_MANAGER 持有者)应用 dangerous
转换,您应使用如下配置:
workflow_extensions: workflows: simple: guard: expression: 'not container.get("access_checker").isGranted("ROLE_USER")' transitions: dangerous: 'not container.get("access_checker").isGranted("ROLE_MANAGER")' subject_manipulator: My\Bundle\Entity\Order: ~ context: access_checker: ~
请注意,这里我们再次使用了由 ExpressionLanguage 评估的表达式,以及代表 DI 容器的容器变量,允许使用公共服务来决定是否阻塞转换。
上下文
当表达式使用某些容器服务时,它将通过 container.get()
方法从容器中获取。由于 Symfony 4 中无法以这种方式从容器中获取私有服务,因此在表达式中访问所需服务时,必须明确在包配置中公开。这是在包配置中的 context
数组中完成的。
workflow_extensions: ... context: # This will expose "doctrine" service from DI under "doctrine" alias inside expression container doctrine: ~ # This will expose "security.authorization_checker" from DI and make it available under # "auth_checker" alias inside expression container auth_checker: 'security.authorization_checker' workflows: simple: guard: expression: 'not container.get("auth_checker").isGranted("ROLE_USER")' transitions: dangerous: 'not container.get("auth_checker").isGranted("ROLE_MANAGER")'
测试
WorkflowExtensionsBundle 由单元测试和功能测试覆盖。如果您对捆绑包的工作方式有疑问,功能测试 可以更清楚地说明捆绑包是如何工作的。