BPMN 2.0 - ProcessMaker Nayra 扩展包
简介
ProcessMaker Nayra 一个基于 BPMN 2.0 标准的 PHP 流程引擎库。它允许您使用 PHP 语言实现、执行和管理业务流程。这个库旨在为开发者提供一个易于使用、可扩展的框架来构建基于 BPMN 2.0 规范的流程引擎。以下是有关 ProcessMaker Nayra 扩展包的详细解释:
BPMN 2.0 兼容性: ProcessMaker Nayra 库支持 BPMN 2.0 规范中的大部分元素,如任务、事件、网关、泳道等。这意味着您可以使用这个库构建符合 BPMN 2.0 标准的流程模型。
对象模型和存储库: ProcessMaker Nayra 使用面向对象的设计,定义了一组接口和类来表示 BPMN 2.0 的元素。例如,
ActivityInterface
表示活动,TokenInterface
表示流程中的令牌等。此外,这个库提供了一个存储库接口(Repository Interface),用于将流程对象持久化到数据库或其他存储系统。执行和管理流程实例: ProcessMaker Nayra 库提供了用于执行和管理流程实例的方法。例如,您可以创建流程实例、触发事件、执行任务等。库中的
Engine
类是流程引擎的核心,它负责处理流程实例的生命周期和执行。事件驱动和事件监听器: ProcessMaker Nayra 支持事件驱动编程,允许您通过定义事件监听器来自定义流程执行的行为。例如,您可以在任务开始或结束时触发自定义逻辑,或在流程实例结束时发送通知等。
可扩展性和灵活性: ProcessMaker Nayra 设计为易于扩展和自定义。您可以通过实现存储库接口、定义自定义事件监听器等方式来扩展库的功能。此外,您可以通过依赖注入和服务容器等技术,将这个库集成到您的 PHP 应用程序中。
总之,ProcessMaker Nayra 扩展包是一个功能强大、易于使用的 PHP 流程引擎库。它允许您使用 PHP 语言构建、执行和管理符合 BPMN 2.0 规范的业务流程。通过学习这个库,您可以更好地理解 BPMN 2.0 标准,并将其应用于实际项目中
安装
使用 Composer 安装 ProcessMaker Nayra 扩展包:
composer require processmaker/nayra
目录结构
src/
├── Bpmn/ # 该目录包含与业务流程建模表示法(BPMN)相关的类。包括 Events(事件类)和 Models(模型类)子目录。这些类代表了BPMN流程中的各种构件,如活动、事件、网关等
├── Events/ # 事件类
├── Events/ # 模型类
├── Contracts/ # 该目录包含与扩展包中实现的接口相关的类。这些接口在Bpmn、Engine和Repositories子目录中定义,允许自定义实现与Nayra库的兼容
├── Bpmn/
├── Engine/
├── Repositories/
├── Storage/
├── Engine/ # 该目录包含执行引擎相关的类,包括 EngineTrait、ExecutionInstance 和 JobManagerTrait
├── Exceptions/ # 该目录包含一些自定义异常类,这些异常类用于在处理流程期间捕获特定的错误情况。
├── Storage/ # 该目录包含存储和验证BPMN文档的类。主要的类有 BPMNValidator、BpmnDocument 和 BpmnElement。
核心概念
活动
在 Nayra 扩展包中,活动是指一个流程中的单个步骤。它可以是一个任务、子流程或者其他类型的活动。在实现一个新的活动时,需要扩展 ActivityInterface 接口。
事件
事件用于控制流程的执行。事件可以是启动、中间或结束事件。为实现一个新的事件,需要扩展 EventInterface
接口
元素
元素是流程中的基本构建块。元素可以是活动、事件、网关等。为实现一个新的元素,需要扩展 FlowElementInterface
接口
网关
网关用于控制流程的分支和合并。根据其行为,网关可以是排他、包含、并行或基于事件的。为实现一个新的网关,需要扩展 GatewayInterface
接口。
使用指南
在这一部分,我们将介绍如何使用 Nayra 扩展包创建、加载、执行和管理 BPMN 流程。
接口实现
ProcessMaker Nayra 1.94 版本中需要自己实现几个核心接口
首先需要创建一个 Engine 类,实现 ProcessMaker\Nayra\Contracts\Engine\EngineInterface
接口。可以参考 EngineInterface
中定义的方法,根据需求实现 Engine
类的功能
namespace App\Service;
use ProcessMaker\Nayra\Contracts\Engine\EngineInterface;
class Engine implements EngineInterface
{
// 实现 EngineInterface 中的方法
// ...
}
接下来,创建一个 BpmnDocumentRepository 类,实现 ProcessMaker\Nayra\Contracts\Storage\RepositoryInterface
接口。同样可以参考 RepositoryInterface
中定义的方法,根据需求实现 BpmnDocumentRepository
类的功能。
namespace App\Service;
use ProcessMaker\Nayra\Contracts\Storage\RepositoryInterface;
class BpmnDocumentRepository implements RepositoryInterface
{
// 实现 RepositoryInterface 中的方法
// ...
}
加载 BPMN 文件
首先,需要加载一个 BPMN 文件。可以使用 BpmnDocument
类加载和解析一个 BPMN 文件:
use ProcessMaker\Nayra\Storage\BpmnDocument;
$document = new BpmnDocument();
$document->load('path/to/your/bpmn-file.bpmn');
创建引擎和存储库
接下来,需要创建一个执行引擎和一个 BPMN 文档存储库。引擎用于执行流程,而存储库用于存储和检索流程定义:
use App\Service\BpmnDocumentRepository;
use App\Service\Engine;
$repository = new BpmnDocumentRepository();
$engine = new Engine($repository);
注册流程定义
将 BPMN 文档注册到存储库中:
$repository->addDocument($document);
$process = $document->getElementsByTagNameNS(BpmnDocument::BPMN_MODEL, 'process')->item(0);
$processId = $process->getAttribute('id');
$processDefinition = $repository->getProcess($processId);
创建流程实例
创建一个流程实例并启动它:
$instance = $engine->createExecutionInstance($processDefinition);
$engine->runToNextState();
运行流程
在一个应用程序中,通常会有一个循环来驱动流程引擎的执行。这个循环将继续运行,直到所有活动和任务都完成:
while ($engine->hasPendingActivities()) {
$engine->runToNextState();
}
解析 BPMN XML
<bpmn:serviceTask id="node_8" name="Form Task" pm:implementation="BetTask" pm:screenRef="2" pm:allowInterstitial="false" pm:assignment="requester" pm:assignmentLock="false" pm:allowReassignment="false">
<bpmn:incoming>node_11</bpmn:incoming>
<bpmn:outgoing>node_3</bpmn:outgoing>
</bpmn:serviceTask>
我们可以看到一个 serviceTask 元素,该元素具有以下属性和子元素:
id="node_8"
:任务的唯一标识符。name="Form Task"
:任务的名称。pm:implementation="BetTask"
:自定义属性,定义了任务实现的类名称。pm:screenRef="2"
:自定义属性,定义了与任务关联的表单的引用。pm:allowInterstitial="false"
:自定义属性,定义了是否允许在任务之间显示过渡页面。pm:assignment="requester"
:自定义属性,定义了任务的分配策略。pm:assignmentLock="false"
:自定义属性,定义了是否锁定任务分配。pm:allowReassignment="false"
:自定义属性,定义了是否允许重新分配任务。
子元素包括:
<bpmn:incoming>node_11</bpmn:incoming>
:定义了一个指向此 serviceTask 的输入连线。输入连线的来源是具有 IDnode_11
的元素。<bpmn:outgoing>node_3</bpmn:outgoing>
:定义了一个从此 serviceTask 指向另一个元素的输出连线。输出连线的目标是具有 IDnode_3
的元素。
总结:这是一个名为“Form Task”的服务任务。它使用名为“BetTask”的实现类,并关联了表单引用“2”。任务分配给请求者,不允许过渡页面,任务分配不会被锁定,也不允许重新分配。这个任务的输入连线来自 ID 为 node_11
的元素,输出连线指向 ID 为 node_3
的元素。
自定义任务
要创建一个自定义任务,请扩展 TaskInterface
接口,并实现 run()
方法。例如自定义一个表单活动,表单中有输入框和下拉选项,当执行这个活动时候,我需要获取表单的值提交到远程接口,然后将接口返回值设置到流程引擎中
创建一个包含 ServiceTask
活动的 BPMN XML 文件
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" />
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="FormTask_1" />
<!-- formActivity Task -->
<bpmn:formActivityTask id="FormTask_1" implementation="FormActivityHandler">
<bpmn:extensionElements>
<form:formData xmlns:form="http://example.com/form">
<form:input id="input1" type="text" />
<form:select id="select1">
<form:option value="option1">Option 1</form:option>
<form:option value="option2">Option 2</form:option>
</form:select>
</form:formData>
</bpmn:extensionElements>
</bpmn:formActivityTask>
<bpmn:endEvent id="EndEvent_1" />
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="FormTask_1" targetRef="EndEvent_1" />
</bpmn:process>
</bpmn:definitions>
创建一个自定义表单活动处理器类 FormActivityTask
基础 ServiceTask
实现 ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface
接口。在这个类中,处理表单数据的获取、远程接口调用以及返回值设置等逻辑。
<?php
namespace App\Services\Tasks;
use ProcessMaker\Nayra\Bpmn\ActivityTrait;
use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;
class FormActivityTask implements ServiceTaskInterface
{
use ActivityTrait;
const TAG_NAME = 'formActivityTask';
/**
* 返回一个数组,该数组定义了 BPMN 元素的自定义事件类。
* @return array
*/
protected function getBpmnEventClasses(): array
{
return[];
}
/**
* 设置服务任务的实现。实现可以是一个可调用的结构(如闭包、函数名或类方法名)
* @param mixed $implementation
* @return BetTask
*/
public function setImplementation(mixed $implementation): static
{
return $this;
}
/**
* 获取服务任务的实现。实现可以是一个可调用的结构(如闭包、函数名或类方法名)
* @return mixed
*/
public function getImplementation(): mixed
{
return null;
}
/**
* 执行服务任务。首先尝试执行服务任务实现(通过 executeService() 方法),如果执行成功,调用 complete() 方法完成任务;否则,将令牌设置为失败状态
* @param TokenInterface $token
* @return BetTask|$this
*/
public function run(TokenInterface $token): BetTask|static
{
dump('formActivityTask run');
if ($data = $this->executeService($token)) {
$dataStore = $token->getInstance()->getDataStore();
$dataStore->putData('issue', $data['issue']);
$this->complete($token);
} else {
// 如果请求失败,将令牌设置为失败状态
$token->setStatus(ActivityInterface::TOKEN_STATE_FAILING);
}
return $this;
}
/**
* 服务任务执行器,用于执行服务任务的实现。这里的实现仅用于测试目的。这个方法尝试调用服务任务的实现,如果调用成功返回 true,否则返回 false。
* @param TokenInterface $token
* @return int[]
*/
private function executeService(TokenInterface $token): array
{
// 在这里实现您的远程请求接口逻辑,例如发送 HTTP 请求
$dataStore = $token->getInstance()->getDataStore();
$dataStore->getData('berOrder');
return [
'issue' => 100,
'value' => 562,
];
}
private function getFormData()
{
// 获取扩展元素中的表单数据
$bpmnElement = $this->getBpmnElement();
$formDataElement = $bpmnElement->getElementsByTagNameNS('yourNamespace', 'formData')->item(0);
// 解析输入字段
$inputFields = $formDataElement->getElementsByTagNameNS('yourNamespace', 'inputField');
$inputs = [];
foreach ($inputFields as $inputField) {
$inputs[] = [
'id' => $inputField->getAttribute('id'),
'label' => $inputField->getAttribute('label'),
];
}
// 解析选择字段
$selectFields = $formDataElement->getElementsByTagNameNS('yourNamespace', 'selectField');
$selects = [];
foreach ($selectFields as $selectField) {
$options = [];
$optionElements = $selectField->getElementsByTagNameNS('yourNamespace', 'option');
foreach ($optionElements as $optionElement) {
$options[] = [
'value' => $optionElement->getAttribute('value'),
'label' => $optionElement->getAttribute('label'),
];
}
$selects[] = [
'id' => $selectField->getAttribute('id'),
'label' => $selectField->getAttribute('label'),
'options' => $options,
];
}
return [
'inputs' => $inputs,
'selects' => $selects,
];
}
}
在 ProcessMaker Nayra 引擎中注册自定义活动处理器
<?php
$bpmnDocument = new BpmnDocument(new RepositoryEngine(), new BpmnEngine());
$bpmnDocument->setBpmnElementMapping('http://www.omg.org/spec/BPMN/20100524/MODEL', FormActivityTask::TAG_NAME, [
FormActivityTask::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [BpmnDocument::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [BpmnDocument::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
ActivityInterface::BPMN_PROPERTY_LOOP_CHARACTERISTICS => ['1', LoopCharacteristicsInterface::class],
ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION => ['1', [BpmnDocument::BPMN_MODEL, ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION]],
],
]);
在上面示例中,我们注册了一个 formActivityTask 活动处理器,分析:RepositoryEngine
实现了 RepositoryInterface 接口的类,需要自己实现,EventEngine
实现了 EventBusInterface 接口的类,需要自己实现
这连个类我们在后面讨论会给出示例代码
class RepositoryEngine implements RepositoryInterface
class EventEngine implements EventBusInterface
注册的格式参考 ProcessMaker\Nayra\Storage\BpmnDocument
类,
namespace ProcessMaker\Nayra\Storage;
use .....
/**
* BPMN file */class BpmnDocument extends DOMDocument implements BpmnDocumentInterface
{
const BPMN_MODEL = 'http://www.omg.org/spec/BPMN/20100524/MODEL';
......
private $mapping = [
'http://www.omg.org/spec/BPMN/20100524/MODEL' => [
'process' => [
ProcessInterface::class,
[
'activities' => ['n', ActivityInterface::class],
'gateways' => ['n', GatewayInterface::class],
'events' => ['n', EventInterface::class],
ProcessInterface::BPMN_PROPERTY_LANE_SET => ['n', [self::BPMN_MODEL, ProcessInterface::BPMN_PROPERTY_LANE_SET]],
],
],
'startEvent' => [
StartEventInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
StartEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS => ['n', EventDefinitionInterface::class],
],
],
'endEvent' => [
EndEventInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
EndEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS => ['n', EventDefinitionInterface::class],
],
],
'task' => [
ActivityInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
ActivityInterface::BPMN_PROPERTY_LOOP_CHARACTERISTICS => ['1', LoopCharacteristicsInterface::class],
ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION => ['1', [self::BPMN_MODEL, ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION]],
],
],
'userTask' => [
ActivityInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
ActivityInterface::BPMN_PROPERTY_LOOP_CHARACTERISTICS => ['1', LoopCharacteristicsInterface::class],
ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION => ['1', [self::BPMN_MODEL, ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION]],
],
],
'scriptTask' => [
ScriptTaskInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
ScriptTaskInterface::BPMN_PROPERTY_SCRIPT => ['1', [self::BPMN_MODEL, ScriptTaskInterface::BPMN_PROPERTY_SCRIPT]],
ActivityInterface::BPMN_PROPERTY_LOOP_CHARACTERISTICS => ['1', LoopCharacteristicsInterface::class],
ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION => ['1', [self::BPMN_MODEL, ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION]],
],
],
'serviceTask' => [
ServiceTaskInterface::class,
[
FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]],
FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]],
ActivityInterface::BPMN_PROPERTY_LOOP_CHARACTERISTICS => ['1', LoopCharacteristicsInterface::class],
ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION => ['1', [self::BPMN_MODEL, ActivityInterface::BPMN_PROPERTY_IO_SPECIFICATION]],
],
],
执行任务
//一个令牌到达第一个任务
$firstTask = $this->bpmnDocument->getServiceTask('serviceTask_1');
$token = $firstTask->getTokens($instance)->item(0);
// 完成第一个任务
$firstTask->run($token);
serviceTask_1
为 BPMN XML 我们自定义活动绑定的 id
自定义事件
要创建一个自定义事件,请扩展 EventInterface
接口。例如,创建一个记录日志的事件:
use ProcessMaker\Nayra\Contracts\Bpmn\EventInterface;
class LogEvent implements EventInterface
{
public function fireEvent(TokenInterface $token)
{
// 记录日志的逻辑
}
}
自定义流程元素
要创建一个自定义流程元素,请扩展 FlowElementInterface
接口。例如,创建一个自定义网关:
use ProcessMaker\Nayra\Contracts\Bpmn\GatewayInterface;
class CustomGateway implements GatewayInterface
{
// 实现自定义网关的逻辑
}
ProcessMaker Nayra 完整示例代码
这个示例展示了如何创建一个简单的流程,包括启动事件、一个任务和一个结束事件。然后,我们加载这个流程并运行它。
use ProcessMaker\Nayra\Storage\BpmnDocument;
use App\Service\BpmnDocumentRepository;
use App\Service\Engine;
// 加载 BPMN 文件
$document = new BpmnDocument();
$document->load('path/to/your/bpmn-file.bpmn');
// 创建引擎和存储库
$repository = new BpmnDocumentRepository();
$engine = new Engine($repository);
// 注册流程定义
$repository->addDocument($document);
$process = $document->getElementsByTagNameNS(BpmnDocument::BPMN_MODEL, 'process')->item(0);
$processId = $process->getAttribute('id');
$processDefinition = $repository->getProcess($processId);
// 创建流程实例并运行
$instance = $engine->createExecutionInstance($processDefinition);
$engine->runToNextState();
// 运行流程
while ($engine->hasPendingActivities()) {
$engine->runToNextState();
}
ProcessMaker Nayra 流程分析
要分析一个流程,需要遍历流程的所有元素并对每个元素进行相应的处理。以下是一个简单的流程分析示例,用于计算流程中活动、事件和网关的数量。
use ProcessMaker\Nayra\Contracts\Bpmn\FlowElementInterface;
$activitiesCount = 0;
$eventsCount = 0;
$gatewaysCount = 0;
foreach ($processDefinition->getFlowElements() as $element) {
if ($element instanceof FlowElementInterface) {
if ($element->getBpmnElement()->localName === 'task') {
$activitiesCount++;
} elseif ($element->getBpmnElement()->localName === 'event') {
$eventsCount++;
} elseif ($element->getBpmnElement()->localName === 'gateway') {
$gatewaysCount++;
}
}
}
echo "活动数量:{$activitiesCount}\n";
echo "事件数量:{$eventsCount}\n";
echo "网关数量:{$gatewaysCount}\n";
这个简单的示例仅用于演示如何遍历流程的元素并对它们进行分类。实际上,流程分析可以包括更复杂的计算,例如计算每个活动的平均执行时间、查找具有最长执行时间的活动等。
其他参考
ProcessMaker/processmaker 是一个庞大复杂的工作流项目。基于 laravel
开发
本作品采用《CC 协议》,转载必须注明作者和本文链接