BPMN 2.0 - ProcessMaker Nayra 扩展包

简介

ProcessMaker Nayra 一个基于 BPMN 2.0 标准的 PHP 流程引擎库。它允许您使用 PHP 语言实现、执行和管理业务流程。这个库旨在为开发者提供一个易于使用、可扩展的框架来构建基于 BPMN 2.0 规范的流程引擎。以下是有关 ProcessMaker Nayra 扩展包的详细解释:

  1. BPMN 2.0 兼容性: ProcessMaker Nayra 库支持 BPMN 2.0 规范中的大部分元素,如任务、事件、网关、泳道等。这意味着您可以使用这个库构建符合 BPMN 2.0 标准的流程模型。

  2. 对象模型和存储库: ProcessMaker Nayra 使用面向对象的设计,定义了一组接口和类来表示 BPMN 2.0 的元素。例如,ActivityInterface 表示活动,TokenInterface 表示流程中的令牌等。此外,这个库提供了一个存储库接口(Repository Interface),用于将流程对象持久化到数据库或其他存储系统。

  3. 执行和管理流程实例: ProcessMaker Nayra 库提供了用于执行和管理流程实例的方法。例如,您可以创建流程实例、触发事件、执行任务等。库中的 Engine 类是流程引擎的核心,它负责处理流程实例的生命周期和执行。

  4. 事件驱动和事件监听器: ProcessMaker Nayra 支持事件驱动编程,允许您通过定义事件监听器来自定义流程执行的行为。例如,您可以在任务开始或结束时触发自定义逻辑,或在流程实例结束时发送通知等。

  5. 可扩展性和灵活性: 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 的输入连线。输入连线的来源是具有 ID node_11 的元素。
  • <bpmn:outgoing>node_3</bpmn:outgoing>:定义了一个从此 serviceTask 指向另一个元素的输出连线。输出连线的目标是具有 ID node_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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!