From a47b38bae5fb7561196ec37b46159512bda28bdb Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 17:56:00 -0400 Subject: [PATCH 01/27] test: Pass of data from sub-process to parent process using Message Events --- .../Patterns/files/MessageEventToParent.bpmn | 191 ++++++++++++++++++ .../Patterns/files/MessageEventToParent.json | 21 ++ 2 files changed, 212 insertions(+) create mode 100644 tests/Feature/Patterns/files/MessageEventToParent.bpmn create mode 100644 tests/Feature/Patterns/files/MessageEventToParent.json diff --git a/tests/Feature/Patterns/files/MessageEventToParent.bpmn b/tests/Feature/Patterns/files/MessageEventToParent.bpmn new file mode 100644 index 00000000..3174f2b4 --- /dev/null +++ b/tests/Feature/Patterns/files/MessageEventToParent.bpmn @@ -0,0 +1,191 @@ + + + + + + + + + SequenceFlow_0h21x7r + + + Flow_148wtcr + + + SequenceFlow_0h21x7r + Flow_1r89qx6 + + + Flow_1r89qx6 + Flow_148wtcr + + + Flow_0azzp4i + + + + + + + Flow_0azzp4i + + + + + + Flow_1a3yzli + + + Flow_09pcqxs + + + Flow_1a3yzli + Flow_0ntd3ys + + + + Flow_1r1agfn + Flow_09pcqxs + + + + + + + + Flow_0ntd3ys + Flow_1r1agfn + + + + + + din_fullName + + + + fullName + + + + din_status + + + + status.name + + + + + status.description + + + + + din_fullName + din_status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Feature/Patterns/files/MessageEventToParent.json b/tests/Feature/Patterns/files/MessageEventToParent.json new file mode 100644 index 00000000..b182abbe --- /dev/null +++ b/tests/Feature/Patterns/files/MessageEventToParent.json @@ -0,0 +1,21 @@ +[ + { + "comment": "Message event sent multiple data inputs to parent", + "startEvent": "StartEvent", + "data": { + }, + "result": [ + "Task_B", + "Task_D", + "Task_E", + "Task_F" + ], + "output": { + "fullName": "admin user", + "status": { + "name": "COMPLETED", + "description": "The task has been completed" + } + } + } +] From 5bb5d60912835302af81493b70399a4754ed17b1 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 17:57:08 -0400 Subject: [PATCH 02/27] feat: Add support for Data Input Associations and Assignment properties --- .../Nayra/Storage/BpmnDocument.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index 70c7d29e..ca3c2b59 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -6,11 +6,13 @@ use DOMElement; use DOMXPath; use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\AssignmentInterface; use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\CallActivityInterface; use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\CollaborationInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ConditionalEventDefinitionInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\DataInputAssociationInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataInputInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataOutputInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface; @@ -371,6 +373,36 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface FlowNodeInterface::BPMN_PROPERTY_INCOMING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_INCOMING]], FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]], IntermediateThrowEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS => ['n', EventDefinitionInterface::class], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT => ['n', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT]], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_SET => ['1', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_SET]], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION => ['n', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION]], + ], + ], + 'dataInputAssociation' => [ + DataInputAssociationInterface::class, + [ + DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF]], + DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT => ['n', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT]], + ], + ], + DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => [self::IS_REFERENCE, []], + DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT => [ + AssignmentInterface::class, + [ + AssignmentInterface::BPMN_PROPERTY_FROM => ['1', [self::BPMN_MODEL, AssignmentInterface::BPMN_PROPERTY_FROM]], + AssignmentInterface::BPMN_PROPERTY_TO => ['1', [self::BPMN_MODEL, AssignmentInterface::BPMN_PROPERTY_TO]], + ], + ], + AssignmentInterface::BPMN_PROPERTY_FROM => [ + FormalExpressionInterface::class, + [ + FormalExpressionInterface::BPMN_PROPERTY_BODY => ['1', self::DOM_ELEMENT_BODY], + ], + ], + AssignmentInterface::BPMN_PROPERTY_TO => [ + FormalExpressionInterface::class, + [ + FormalExpressionInterface::BPMN_PROPERTY_BODY => ['1', self::DOM_ELEMENT_BODY], ], ], 'signalEventDefinition' => [ From 32d1763d9d8f34cd05739ef6aaef2d89e345852e Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 17:57:30 -0400 Subject: [PATCH 03/27] feat: Introduce new properties and methods for BPMN interfaces --- .../Nayra/Contracts/Bpmn/AssignmentInterface.php | 3 +++ .../Nayra/Contracts/Bpmn/DataAssociationInterface.php | 2 +- .../Contracts/Bpmn/DataInputAssociationInterface.php | 2 ++ .../Nayra/Contracts/Bpmn/DataStoreInterface.php | 10 ++++++++++ .../Nayra/Contracts/Bpmn/EventInterface.php | 3 +++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/AssignmentInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/AssignmentInterface.php index 20069e60..9a25a1f2 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/AssignmentInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/AssignmentInterface.php @@ -7,6 +7,9 @@ */ interface AssignmentInterface extends EntityInterface { + const BPMN_PROPERTY_FROM = 'from'; + const BPMN_PROPERTY_TO = 'to'; + /** * @return FormalExpressionInterface */ diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php index b12f5c69..61da646a 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php @@ -31,5 +31,5 @@ public function getTransformation(); /** * @return AssignmentInterface[] */ - public function getAssignmentInterfaces(); + public function getAssignments(); } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php index 27c817fa..eb13d656 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php @@ -7,4 +7,6 @@ */ interface DataInputAssociationInterface extends DataAssociationInterface { + const BPMN_PROPERTY_TARGET_REF = 'targetRef'; + const BPMN_PROPERTY_ASSIGNMENT = 'assignment'; } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php index 3fef523f..d6f85034 100755 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php @@ -76,4 +76,14 @@ public function setData($data); * @return $this */ public function putData($name, $data); + + /** + * Set data using dot notation. + * + * @param string $path Dot notation path (e.g., 'user.profile.name') + * @param mixed $value Value to set + * + * @return $this + */ + public function setDotData($path, $value); } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/EventInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/EventInterface.php index ef4845f2..6ea16164 100755 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/EventInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/EventInterface.php @@ -10,6 +10,9 @@ interface EventInterface extends FlowNodeInterface { const BPMN_PROPERTY_EVENT_DEFINITIONS = 'eventDefinitions'; + const BPMN_PROPERTY_DATA_INPUT = 'dataInput'; + const BPMN_PROPERTY_DATA_INPUT_SET = 'inputSet'; + const BPMN_PROPERTY_DATA_INPUT_ASSOCIATION = 'dataInputAssociation'; const TYPE_START = 'START'; From ddfc7fef94404a06b5e7093f5e7dcdf611f72d6f Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 17:57:56 -0400 Subject: [PATCH 04/27] feat: Add methods to create DataInputAssociation and Assignment in RepositoryTrait --- src/ProcessMaker/Nayra/RepositoryTrait.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ProcessMaker/Nayra/RepositoryTrait.php b/src/ProcessMaker/Nayra/RepositoryTrait.php index e56bac84..e53cdb79 100644 --- a/src/ProcessMaker/Nayra/RepositoryTrait.php +++ b/src/ProcessMaker/Nayra/RepositoryTrait.php @@ -3,6 +3,8 @@ namespace ProcessMaker\Nayra; use InvalidArgumentException; +use ProcessMaker\Nayra\Bpmn\Assignment; +use ProcessMaker\Nayra\Bpmn\DataInputAssociation; use ProcessMaker\Nayra\Bpmn\Lane; use ProcessMaker\Nayra\Bpmn\LaneSet; use ProcessMaker\Nayra\Bpmn\Models\Activity; @@ -481,4 +483,14 @@ public function createStandardLoopCharacteristics() { return new StandardLoopCharacteristics(); } + + public function createDataInputAssociation() + { + return new DataInputAssociation(); + } + + public function createAssignment() + { + return new Assignment(); + } } From 37c6a8c218bdfbf89b3a435261de7ddf5747c4e5 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 17:58:14 -0400 Subject: [PATCH 05/27] feat: Implement Assignment and DataInputAssociation classes with updated BaseTrait methods --- src/ProcessMaker/Nayra/Bpmn/Assignment.php | 34 ++++++++++++++++ src/ProcessMaker/Nayra/Bpmn/BaseTrait.php | 4 ++ .../Nayra/Bpmn/DataInputAssociation.php | 27 +++++++++++++ .../Nayra/Bpmn/DataStoreTrait.php | 39 +++++++++++++++++++ .../Bpmn/Models/IntermediateThrowEvent.php | 18 ++------- .../Bpmn/Models/MessageEventDefinition.php | 27 +++++++++++++ 6 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 src/ProcessMaker/Nayra/Bpmn/Assignment.php create mode 100644 src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php diff --git a/src/ProcessMaker/Nayra/Bpmn/Assignment.php b/src/ProcessMaker/Nayra/Bpmn/Assignment.php new file mode 100644 index 00000000..6ec6d1f0 --- /dev/null +++ b/src/ProcessMaker/Nayra/Bpmn/Assignment.php @@ -0,0 +1,34 @@ +getProperty(self::BPMN_PROPERTY_FROM); + } + + /** + * Get the 'to' formal expression. + * + * @return FormalExpressionInterface + */ + public function getTo() + { + return $this->getProperty(self::BPMN_PROPERTY_TO); + } +} diff --git a/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php b/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php index eff26f14..9c3def39 100755 --- a/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php @@ -204,6 +204,10 @@ public function addProperty($name, $value) { $this->properties[$name] = isset($this->properties[$name]) ? $this->properties[$name] : new Collection; $this->properties[$name]->push($value); + $localNameSetter = 'set' . $name . 's'; + if (isset($this->$localNameSetter)) { + $this->$localNameSetter($this->properties[$name]); + } return $this; } diff --git a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php new file mode 100644 index 00000000..29a6255d --- /dev/null +++ b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php @@ -0,0 +1,27 @@ +getProperty(static::BPMN_PROPERTY_TARGET_REF); + } + + public function getTransformation() {} + + public function getAssignments() + { + return $this->getProperty(static::BPMN_PROPERTY_ASSIGNMENT); + } +} diff --git a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php index 47eb66dd..67b91546 100755 --- a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php @@ -100,4 +100,43 @@ public function getItemSubject() { return $this->itemSubject; } + + /** + * Set data using dot notation. + * + * @param string $path Dot notation path (e.g., 'user.profile.name') + * @param mixed $value Value to set + * + * @return $this + */ + public function setDotData($path, $value) + { + $keys = explode('.', $path); + $current = &$this->data; + + // Navigate to the parent of the target key + for ($i = 0; $i < count($keys) - 1; $i++) { + $key = $keys[$i]; + + // Handle numeric keys for arrays + if (is_numeric($key)) { + $key = (int) $key; + } + + if (!isset($current[$key]) || !is_array($current[$key])) { + $current[$key] = []; + } + $current = &$current[$key]; + } + + // Set the final value + $finalKey = $keys[count($keys) - 1]; + if (is_numeric($finalKey)) { + $finalKey = (int) $finalKey; + } + + $current[$finalKey] = $value; + + return $this; + } } diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/IntermediateThrowEvent.php b/src/ProcessMaker/Nayra/Bpmn/Models/IntermediateThrowEvent.php index a9977a2e..4edfec67 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/IntermediateThrowEvent.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/IntermediateThrowEvent.php @@ -13,16 +13,6 @@ class IntermediateThrowEvent implements IntermediateThrowEventInterface { use IntermediateThrowEventTrait; - /** - * @var \ProcessMaker\Nayra\Contracts\Bpmn\DataInputAssociationInterface[] - */ - private $dataInputAssociations; - - /** - * @var \ProcessMaker\Nayra\Contracts\Bpmn\DataInputInterface[] - */ - private $dataInputs; - /** * @var \ProcessMaker\Nayra\Contracts\Bpmn\InputSetInterface */ @@ -38,8 +28,8 @@ class IntermediateThrowEvent implements IntermediateThrowEventInterface */ protected function initIntermediateThrowEvent() { - $this->dataInputAssociations = new Collection; - $this->dataInputs = new Collection; + $this->properties[static::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION] = new Collection; + $this->properties[static::BPMN_PROPERTY_DATA_INPUT] = new Collection; $this->setProperty(static::BPMN_PROPERTY_EVENT_DEFINITIONS, new Collection); } @@ -60,7 +50,7 @@ protected function getBpmnEventClasses() */ public function getDataInputAssociations() { - return $this->dataInputAssociations; + return $this->getProperty(static::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION); } /** @@ -70,7 +60,7 @@ public function getDataInputAssociations() */ public function getDataInputs() { - return $this->dataInputs; + return $this->getProperty(static::BPMN_PROPERTY_DATA_INPUT); } /** diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index 77bae714..c345ee2f 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -8,6 +8,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\MessageEventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\MessageInterface; use ProcessMaker\Nayra\Contracts\Bpmn\OperationInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\ThrowEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; @@ -88,9 +89,35 @@ public function assertsRule(EventDefinitionInterface $event, FlowNodeInterface $ */ public function execute(EventDefinitionInterface $event, FlowNodeInterface $target, ExecutionInstanceInterface $instance = null, TokenInterface $token = null) { + $throwEvent = $token->getOwnerElement(); + $this->evaluateMessagePayload($throwEvent, $token, $instance); return $this; } + private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenInterface $token, ExecutionInstanceInterface $targetInstance) + { + $dataInputs = $throwEvent->getDataInputs(); + // Initialize message payload + $payload = []; + // Associate data inputs to message payload + $associations = $throwEvent->getDataInputAssociations(); + $data = $token->getInstance()->getDataStore()->getData(); + foreach ($associations as $association) { + $assignments = $association->getAssignments(); + foreach ($assignments as $assignment) { + $from = $assignment->getFrom(); + $to = trim($assignment->getTo()->getBody()); + // Dot Notation + $payload[] = ['key' => $to, 'value' => $from($data)]; + } + } + // Update data into target $instance + $dataStore = $targetInstance->getDataStore(); + foreach ($payload as $load) { + $dataStore->setDotData($load['key'], $load['value']); + } + } + /** * Check if the $eventDefinition should be catch * From e375107239723af779f1f742671a2a2db4313f17 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 18:07:02 -0400 Subject: [PATCH 06/27] fix: Refactor EndEvent class to initialize properties and update data input methods --- .../Nayra/Bpmn/Models/EndEvent.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/EndEvent.php b/src/ProcessMaker/Nayra/Bpmn/Models/EndEvent.php index 53e5a0ea..04c605ac 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/EndEvent.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/EndEvent.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Nayra\Bpmn\Models; +use ProcessMaker\Nayra\Bpmn\Collection; use ProcessMaker\Nayra\Bpmn\EndEventTrait; use ProcessMaker\Nayra\Contracts\Bpmn\EndEventInterface; use ProcessMaker\Nayra\Model\DataInputAssociationInterface; @@ -19,12 +20,18 @@ class EndEvent implements EndEventInterface { use EndEventTrait; - private $dataInputs; - - private $dataInputAssociations; - private $inputSet; + /** + * Initialize intermediate throw event. + */ + protected function initEndEvent() + { + $this->properties[static::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION] = new Collection(); + $this->properties[static::BPMN_PROPERTY_DATA_INPUT] = new Collection; + $this->setProperty(static::BPMN_PROPERTY_EVENT_DEFINITIONS, new Collection); + } + /** * Array map of custom event classes for the bpmn element. * @@ -52,7 +59,7 @@ public function getTargetInstances(EventDefinitionInterface $message, TokenInter */ public function getDataInputs() { - return $this->dataInputs; + return $this->getProperty(static::BPMN_PROPERTY_DATA_INPUT); } /** @@ -62,7 +69,7 @@ public function getDataInputs() */ public function getDataInputAssociations() { - return $this->getDataInputAssociations(); + return $this->getProperty(static::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION); } /** From 7ea851850530a60a134c6c247885e08d9fc9cc71 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 30 Sep 2025 19:15:14 -0400 Subject: [PATCH 07/27] chore: Update SonarQube workflow to check coverage using a dedicated script --- .github/workflows/sonarqube.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index db3c45a3..3f6d74ce 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -44,8 +44,8 @@ jobs: - name: Run PHPUnit tests run: ./vendor/bin/phpunit -d memory_limit=-1 - - name: List coverage files - run: ls -l coverage + - name: Check coverage + run: ./check_coverage.php - uses: sonarsource/sonarqube-scan-action@master env: From e9c5b8a59406b612f5faf8a1d8109882d9732f6c Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 09:05:43 -0400 Subject: [PATCH 08/27] Remove non required code --- src/ProcessMaker/Nayra/Bpmn/BaseTrait.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php b/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php index 9c3def39..eff26f14 100755 --- a/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/BaseTrait.php @@ -204,10 +204,6 @@ public function addProperty($name, $value) { $this->properties[$name] = isset($this->properties[$name]) ? $this->properties[$name] : new Collection; $this->properties[$name]->push($value); - $localNameSetter = 'set' . $name . 's'; - if (isset($this->$localNameSetter)) { - $this->$localNameSetter($this->properties[$name]); - } return $this; } From 0b75eb8323b6ce6b243157a211b7d225d6210ab1 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 09:06:56 -0400 Subject: [PATCH 09/27] fix: Update return type annotations in Assignment class methods to include callable --- src/ProcessMaker/Nayra/Bpmn/Assignment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Assignment.php b/src/ProcessMaker/Nayra/Bpmn/Assignment.php index 6ec6d1f0..f4c99d2a 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Assignment.php +++ b/src/ProcessMaker/Nayra/Bpmn/Assignment.php @@ -15,7 +15,7 @@ class Assignment implements AssignmentInterface /** * Get the 'from' formal expression. * - * @return FormalExpressionInterface + * @return FormalExpressionInterface|callable */ public function getFrom() { @@ -25,7 +25,7 @@ public function getFrom() /** * Get the 'to' formal expression. * - * @return FormalExpressionInterface + * @return FormalExpressionInterface|callable */ public function getTo() { From 1d13a5215365236799b9295e7f8157d52c434d58 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 09:08:39 -0400 Subject: [PATCH 10/27] feat: Implement getSources and getTransformation methods in DataInputAssociation class --- src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php | 10 ++++++++-- .../Contracts/Bpmn/DataInputAssociationInterface.php | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php index 29a6255d..8dbb0818 100644 --- a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php +++ b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php @@ -11,14 +11,20 @@ class DataInputAssociation implements DataInputAssociationInterface { use BaseTrait; - public function getSources() {} + public function getSources() + { + return $this->getProperty(static::BPMN_PROPERTY_SOURCES_REF); + } public function getTarget() { return $this->getProperty(static::BPMN_PROPERTY_TARGET_REF); } - public function getTransformation() {} + public function getTransformation() + { + return $this->getProperty(static::BPMN_PROPERTY_TRANSFORMATION); + } public function getAssignments() { diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php index eb13d656..97cca901 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php @@ -7,6 +7,8 @@ */ interface DataInputAssociationInterface extends DataAssociationInterface { - const BPMN_PROPERTY_TARGET_REF = 'targetRef'; const BPMN_PROPERTY_ASSIGNMENT = 'assignment'; + const BPMN_PROPERTY_SOURCES_REF = 'sourceRef'; + const BPMN_PROPERTY_TARGET_REF = 'targetRef'; + const BPMN_PROPERTY_TRANSFORMATION = 'transformation'; } From 15dc3bc18e916bd1c6642a57cadb756768a49386 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 09:09:31 -0400 Subject: [PATCH 11/27] refactor: Remove unused dataInputs variable --- src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index c345ee2f..e290d24b 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -96,7 +96,6 @@ public function execute(EventDefinitionInterface $event, FlowNodeInterface $targ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenInterface $token, ExecutionInstanceInterface $targetInstance) { - $dataInputs = $throwEvent->getDataInputs(); // Initialize message payload $payload = []; // Associate data inputs to message payload From 6fa2d4abafff63e846eb8c7cf6932f09eed6faeb Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 12:02:00 -0400 Subject: [PATCH 12/27] feat: Enhance MessageEventToParent BPMN to test all inputs and associations --- .../Patterns/files/MessageEventToParent.bpmn | 20 ++++++++++++++----- .../Patterns/files/MessageEventToParent.json | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/Feature/Patterns/files/MessageEventToParent.bpmn b/tests/Feature/Patterns/files/MessageEventToParent.bpmn index 3174f2b4..c3e777ff 100644 --- a/tests/Feature/Patterns/files/MessageEventToParent.bpmn +++ b/tests/Feature/Patterns/files/MessageEventToParent.bpmn @@ -10,6 +10,7 @@ + SequenceFlow_0h21x7r @@ -47,7 +48,7 @@ Flow_1a3yzli Flow_0ntd3ys - + Flow_1r1agfn @@ -65,17 +66,25 @@ Flow_0ntd3ys Flow_1r1agfn - + - + + - din_fullName + din_email + ds_email + + + + din_login + fullName + din_status @@ -91,7 +100,8 @@ - din_fullName + din_email + din_login din_status diff --git a/tests/Feature/Patterns/files/MessageEventToParent.json b/tests/Feature/Patterns/files/MessageEventToParent.json index b182abbe..19a447ad 100644 --- a/tests/Feature/Patterns/files/MessageEventToParent.json +++ b/tests/Feature/Patterns/files/MessageEventToParent.json @@ -11,6 +11,8 @@ "Task_F" ], "output": { + "email": "admin@example.com", + "login": "ADMIN", "fullName": "admin user", "status": { "name": "COMPLETED", From 48b57206367206fe3ee521bac9dfbf9052b238f7 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 12:02:36 -0400 Subject: [PATCH 13/27] feat: Add tests for setDotData and getDotData methods in DataStore class --- .../ProcessMaker/Nayra/Bpmn/DataStoreTest.php | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/tests/unit/ProcessMaker/Nayra/Bpmn/DataStoreTest.php b/tests/unit/ProcessMaker/Nayra/Bpmn/DataStoreTest.php index 1cda857c..eb36138a 100644 --- a/tests/unit/ProcessMaker/Nayra/Bpmn/DataStoreTest.php +++ b/tests/unit/ProcessMaker/Nayra/Bpmn/DataStoreTest.php @@ -35,4 +35,209 @@ public function testDataStoreSettersAndGetters() //Assertion: the data store should have a non initialized item subject $this->assertNull($dataStore->getItemSubject()); } + + /** + * Tests the setDotData function with various scenarios + */ + public function testSetDotData() + { + $dataStore = $this->repository->createDataStore(); + + // Test simple dot notation + $dataStore->setDotData('user.name', 'John Doe'); + $userData = $dataStore->getData('user'); + $this->assertEquals('John Doe', $userData['name']); + + // Test nested dot notation + $dataStore->setDotData('user.profile.email', 'john@example.com'); + $userData = $dataStore->getData('user'); + $this->assertEquals('john@example.com', $userData['profile']['email']); + + // Test deeply nested structure + $dataStore->setDotData('company.departments.engineering.team.lead', 'Jane Smith'); + $companyData = $dataStore->getData('company'); + $this->assertEquals('Jane Smith', $companyData['departments']['engineering']['team']['lead']); + + // Test numeric keys + $dataStore->setDotData('items.0.name', 'First Item'); + $dataStore->setDotData('items.1.name', 'Second Item'); + $itemsData = $dataStore->getData('items'); + $this->assertEquals('First Item', $itemsData[0]['name']); + $this->assertEquals('Second Item', $itemsData[1]['name']); + + // Test numeric final key (to cover the is_numeric check for finalKey) + $dataStore->setDotData('scores.0', 100); + $dataStore->setDotData('scores.1', 200); + $scoresData = $dataStore->getData('scores'); + $this->assertEquals(100, $scoresData[0]); + $this->assertEquals(200, $scoresData[1]); + + // Test overwriting existing values + $dataStore->setDotData('user.name', 'Jane Doe'); + $userData = $dataStore->getData('user'); + $this->assertEquals('Jane Doe', $userData['name']); + + // Test setting complex values + $complexValue = ['type' => 'admin', 'permissions' => ['read', 'write']]; + $dataStore->setDotData('user.role', $complexValue); + $userData = $dataStore->getData('user'); + $this->assertEquals($complexValue, $userData['role']); + + // Test setting null values + $dataStore->setDotData('user.middleName', null); + $userData = $dataStore->getData('user'); + $this->assertNull($userData['middleName']); + + // Test setting boolean values + $dataStore->setDotData('user.active', true); + $userData = $dataStore->getData('user'); + $this->assertTrue($userData['active']); + + // Test setting numeric values + $dataStore->setDotData('user.age', 30); + $userData = $dataStore->getData('user'); + $this->assertEquals(30, $userData['age']); + + // Test that the method returns the data store instance for chaining + $result = $dataStore->setDotData('test.chain', 'value'); + $this->assertSame($dataStore, $result); + + // Verify the complete data structure + $expectedData = [ + 'user' => [ + 'name' => 'Jane Doe', + 'profile' => [ + 'email' => 'john@example.com' + ], + 'role' => [ + 'type' => 'admin', + 'permissions' => ['read', 'write'] + ], + 'middleName' => null, + 'active' => true, + 'age' => 30 + ], + 'company' => [ + 'departments' => [ + 'engineering' => [ + 'team' => [ + 'lead' => 'Jane Smith' + ] + ] + ] + ], + 'items' => [ + 0 => [ + 'name' => 'First Item' + ], + 1 => [ + 'name' => 'Second Item' + ] + ], + 'scores' => [ + 0 => 100, + 1 => 200 + ], + 'test' => [ + 'chain' => 'value' + ] + ]; + + $this->assertEquals($expectedData, $dataStore->getData()); + } + + /** + * Tests the getDotData function with various scenarios + */ + public function testGetDotData() + { + $dataStore = $this->repository->createDataStore(); + + // Set up test data using setDotData + $dataStore->setDotData('user.name', 'John Doe'); + $dataStore->setDotData('user.profile.email', 'john@example.com'); + $dataStore->setDotData('user.profile.age', 30); + $dataStore->setDotData('user.active', true); + $dataStore->setDotData('user.role', ['type' => 'admin', 'permissions' => ['read', 'write']]); + $dataStore->setDotData('company.departments.engineering.team.lead', 'Jane Smith'); + $dataStore->setDotData('items.0.name', 'First Item'); + $dataStore->setDotData('items.1.name', 'Second Item'); + $dataStore->setDotData('scores.0', 100); + $dataStore->setDotData('scores.1', 200); + $dataStore->setDotData('user.middleName', null); + + // Test simple dot notation retrieval + $this->assertEquals('John Doe', $dataStore->getDotData('user.name')); + $this->assertEquals('john@example.com', $dataStore->getDotData('user.profile.email')); + $this->assertEquals(30, $dataStore->getDotData('user.profile.age')); + $this->assertTrue($dataStore->getDotData('user.active')); + + // Test nested dot notation retrieval + $this->assertEquals('Jane Smith', $dataStore->getDotData('company.departments.engineering.team.lead')); + + // Test numeric keys + $this->assertEquals('First Item', $dataStore->getDotData('items.0.name')); + $this->assertEquals('Second Item', $dataStore->getDotData('items.1.name')); + + // Test numeric final keys + $this->assertEquals(100, $dataStore->getDotData('scores.0')); + $this->assertEquals(200, $dataStore->getDotData('scores.1')); + + // Test complex values + $expectedRole = ['type' => 'admin', 'permissions' => ['read', 'write']]; + $this->assertEquals($expectedRole, $dataStore->getDotData('user.role')); + + // Test null values + $this->assertNull($dataStore->getDotData('user.middleName')); + + // Test non-existent paths with default values + $this->assertNull($dataStore->getDotData('non.existent.path')); + $this->assertEquals('default', $dataStore->getDotData('non.existent.path', 'default')); + $this->assertEquals('fallback', $dataStore->getDotData('user.nonExistent', 'fallback')); + + // Test partial path that doesn't exist + $this->assertNull($dataStore->getDotData('user.profile.nonExistent')); + $this->assertEquals('not found', $dataStore->getDotData('user.profile.nonExistent', 'not found')); + + // Test deeply nested non-existent path + $this->assertNull($dataStore->getDotData('company.departments.marketing.team.lead')); + $this->assertEquals('no lead', $dataStore->getDotData('company.departments.marketing.team.lead', 'no lead')); + + // Test numeric key that doesn't exist + $this->assertNull($dataStore->getDotData('items.2.name')); + $this->assertEquals('missing', $dataStore->getDotData('items.2.name', 'missing')); + + // Test empty path + $this->assertNull($dataStore->getDotData('')); + $this->assertEquals('empty', $dataStore->getDotData('', 'empty')); + + // Test single key that exists (returns the entire user array) + $userData = $dataStore->getDotData('user'); + $this->assertIsArray($userData); + $this->assertEquals('John Doe', $userData['name']); + $this->assertEquals('john@example.com', $userData['profile']['email']); + $this->assertEquals(30, $userData['profile']['age']); + $this->assertTrue($userData['active']); + $this->assertNull($userData['middleName']); + + // Test single key that doesn't exist + $this->assertNull($dataStore->getDotData('nonexistent')); + $this->assertEquals('not found', $dataStore->getDotData('nonexistent', 'not found')); + + // Test boolean false value + $dataStore->setDotData('user.disabled', false); + $this->assertFalse($dataStore->getDotData('user.disabled')); + + // Test zero value + $dataStore->setDotData('user.score', 0); + $this->assertEquals(0, $dataStore->getDotData('user.score')); + + // Test empty string + $dataStore->setDotData('user.description', ''); + $this->assertEquals('', $dataStore->getDotData('user.description')); + + // Test empty array + $dataStore->setDotData('user.tags', []); + $this->assertEquals([], $dataStore->getDotData('user.tags')); + } } From 4fa04f93733fa529c5d1e162df2a6aa5765d4ac3 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 12:04:54 -0400 Subject: [PATCH 14/27] feat: Introduce getDotData method for dot notation access --- .../Nayra/Bpmn/DataStoreTrait.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php index 67b91546..a00ac2e3 100755 --- a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php @@ -101,6 +101,36 @@ public function getItemSubject() return $this->itemSubject; } + /** + * Get data using dot notation. + * + * @param string $path Dot notation path (e.g., 'user.profile.name') + * @param mixed $default Default value if path doesn't exist + * + * @return mixed + */ + public function getDotData($path, $default = null) + { + $keys = explode('.', $path); + $current = $this->data; + + // Navigate through the path + foreach ($keys as $key) { + // Handle numeric keys for arrays + if (is_numeric($key)) { + $key = (int) $key; + } + + if (!isset($current[$key])) { + return $default; + } + + $current = $current[$key]; + } + + return $current; + } + /** * Set data using dot notation. * From e031a12b8454e3e6ab6e16a63144dfdd1ac1970e Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 12:06:07 -0400 Subject: [PATCH 15/27] feat: Implements DataInputAssociation Transformation and sourceRef --- .../Nayra/Bpmn/DataInputAssociation.php | 7 +++++- .../Bpmn/Models/MessageEventDefinition.php | 24 +++++++++++++++++-- .../Bpmn/DataAssociationInterface.php | 4 ++-- .../Contracts/Bpmn/DataStoreInterface.php | 10 ++++++++ .../Nayra/Storage/BpmnDocument.php | 9 +++++++ 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php index 8dbb0818..a95b8c54 100644 --- a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php +++ b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php @@ -11,7 +11,12 @@ class DataInputAssociation implements DataInputAssociationInterface { use BaseTrait; - public function getSources() + protected function initDataInputAssociation() + { + $this->properties[static::BPMN_PROPERTY_ASSIGNMENT] = new Collection; + } + + public function getSource() { return $this->getProperty(static::BPMN_PROPERTY_SOURCES_REF); } diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index e290d24b..1210fa13 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -98,10 +98,30 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn { // Initialize message payload $payload = []; - // Associate data inputs to message payload $associations = $throwEvent->getDataInputAssociations(); - $data = $token->getInstance()->getDataStore()->getData(); + // Get data from source token instance + $sourceDataStore = $token->getInstance()->getDataStore(); + + // Associate data inputs to message payload foreach ($associations as $association) { + $data = $sourceDataStore->getData(); + $source = $association->getSource(); + $target = $association->getTarget(); + $transformation = $association->getTransformation(); + + // Add reference to source + $hasSource = $source && $source->getName(); + $data['sourceRef'] = $hasSource ? $sourceDataStore->getDotData($source->getName()) : null; + + // Apply transformation if exists + if ($transformation) { + $value = $transformation($data); + $payload[] = ['key' => $target->getName(), 'value' => $value]; + } elseif ($hasSource) { + $payload[] = ['key' => $target->getName(), 'value' => $data['sourceRef']]; + } + + // Evaluate assignments $assignments = $association->getAssignments(); foreach ($assignments as $assignment) { $from = $assignment->getFrom(); diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php index 61da646a..48cc1c6a 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php @@ -10,9 +10,9 @@ interface DataAssociationInterface extends EntityInterface /** * Get the source of the data association. * - * @return ItemAwareElementInterface[] + * @return ItemAwareElementInterface */ - public function getSources(); + public function getSource(); /** * Get the target of the data association. diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php index d6f85034..0f491500 100755 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php @@ -86,4 +86,14 @@ public function putData($name, $data); * @return $this */ public function setDotData($path, $value); + + /** + * Get data using dot notation. + * + * @param string $path Dot notation path (e.g., 'user.profile.name') + * @param mixed $default Default value if path doesn't exist + * + * @return mixed + */ + public function getDotData($path, $default = null); } diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index ca3c2b59..86bc3985 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -382,10 +382,19 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface DataInputAssociationInterface::class, [ DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF]], + DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF]], DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT => ['n', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT]], + DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION]], ], ], DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => [self::IS_REFERENCE, []], + DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => [self::IS_REFERENCE, []], + DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => [ + FormalExpressionInterface::class, + [ + FormalExpressionInterface::BPMN_PROPERTY_BODY => ['1', self::DOM_ELEMENT_BODY], + ], + ], DataInputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT => [ AssignmentInterface::class, [ From 4c3b90adb657f19e459fbfb292b5d64e20ded94a Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 12:11:07 -0400 Subject: [PATCH 16/27] fix: Add __invoke to Interface to handle cursor bot observation --- .../Nayra/Contracts/Bpmn/FormalExpressionInterface.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/FormalExpressionInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/FormalExpressionInterface.php index 0b6d2147..66a61449 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/FormalExpressionInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/FormalExpressionInterface.php @@ -39,4 +39,13 @@ public function getBody(); * @param string $body */ public function setBody($body); + + /** + * Invoke the format expression. + * + * @param mixed $data + * + * @return string + */ + public function __invoke($data); } From 12fb638723609687d15f0640813fdefb53a08b0b Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 13:31:29 -0400 Subject: [PATCH 17/27] fix: Ensure callable checks for transformation and assignments in MessageEventDefinition --- .../Nayra/Bpmn/Models/MessageEventDefinition.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index 1210fa13..c469dac6 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -111,13 +111,14 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn // Add reference to source $hasSource = $source && $source->getName(); + $hasTarget = $target && $target->getName(); $data['sourceRef'] = $hasSource ? $sourceDataStore->getDotData($source->getName()) : null; // Apply transformation if exists - if ($transformation) { + if ($hasTarget && $transformation && is_callable($transformation)) { $value = $transformation($data); $payload[] = ['key' => $target->getName(), 'value' => $value]; - } elseif ($hasSource) { + } elseif ($hasTarget && $hasSource) { $payload[] = ['key' => $target->getName(), 'value' => $data['sourceRef']]; } @@ -126,8 +127,9 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn foreach ($assignments as $assignment) { $from = $assignment->getFrom(); $to = trim($assignment->getTo()->getBody()); - // Dot Notation - $payload[] = ['key' => $to, 'value' => $from($data)]; + if (is_callable($from)) { + $payload[] = ['key' => $to, 'value' => $from($data)]; + } } } // Update data into target $instance From c9b4223bf5a096d14c519c0474e1e113e8508d00 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 15:08:35 -0400 Subject: [PATCH 18/27] refactor: Refactor evaluateMessagePayload to reduce its Cognitive Complexity --- .../Bpmn/Models/MessageEventDefinition.php | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index c469dac6..ee05e0fd 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -94,6 +94,14 @@ public function execute(EventDefinitionInterface $event, FlowNodeInterface $targ return $this; } + /** + * Evaluate the message payload + * + * @param ThrowEventInterface $throwEvent + * @param TokenInterface $token + * @param ExecutionInstanceInterface $targetInstance + * @return void + */ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenInterface $token, ExecutionInstanceInterface $targetInstance) { // Initialize message payload @@ -107,30 +115,17 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn $data = $sourceDataStore->getData(); $source = $association->getSource(); $target = $association->getTarget(); - $transformation = $association->getTransformation(); // Add reference to source $hasSource = $source && $source->getName(); $hasTarget = $target && $target->getName(); $data['sourceRef'] = $hasSource ? $sourceDataStore->getDotData($source->getName()) : null; - // Apply transformation if exists - if ($hasTarget && $transformation && is_callable($transformation)) { - $value = $transformation($data); - $payload[] = ['key' => $target->getName(), 'value' => $value]; - } elseif ($hasTarget && $hasSource) { - $payload[] = ['key' => $target->getName(), 'value' => $data['sourceRef']]; - } + // Apply transformation + $this->applyTransformation($association, $data, $payload, $hasTarget, $hasSource); // Evaluate assignments - $assignments = $association->getAssignments(); - foreach ($assignments as $assignment) { - $from = $assignment->getFrom(); - $to = trim($assignment->getTo()->getBody()); - if (is_callable($from)) { - $payload[] = ['key' => $to, 'value' => $from($data)]; - } - } + $this->evaluateAssignments($association, $data, $payload); } // Update data into target $instance $dataStore = $targetInstance->getDataStore(); @@ -139,6 +134,47 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn } } + /** + * Apply transformation to the data and add to payload + * + * @param mixed $association + * @param array $data + * @param array &$payload + * @param bool $hasTarget + * @param bool $hasSource + */ + private function applyTransformation($association, array $data, array &$payload, bool $hasTarget, bool $hasSource) + { + $transformation = $association->getTransformation(); + $target = $association->getTarget(); + + if ($hasTarget && $transformation && is_callable($transformation)) { + $value = $transformation($data); + $payload[] = ['key' => $target->getName(), 'value' => $value]; + } elseif ($hasTarget && $hasSource) { + $payload[] = ['key' => $target->getName(), 'value' => $data['sourceRef']]; + } + } + + /** + * Evaluate assignments and add to payload + * + * @param mixed $association + * @param array $data + * @param array &$payload + */ + private function evaluateAssignments($association, array $data, array &$payload) + { + $assignments = $association->getAssignments(); + foreach ($assignments as $assignment) { + $from = $assignment->getFrom(); + $to = trim($assignment->getTo()?->getBody()); + if (is_callable($from)) { + $payload[] = ['key' => $to, 'value' => $from($data)]; + } + } + } + /** * Check if the $eventDefinition should be catch * From 1c68adcd55e94b1efc6037ec1cc02b1d9f9a382e Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 15:16:10 -0400 Subject: [PATCH 19/27] fix: Handle missing data store in MessageEventDefinition by providing a default instance --- src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index ee05e0fd..03d7ecb7 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -107,8 +107,8 @@ private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenIn // Initialize message payload $payload = []; $associations = $throwEvent->getDataInputAssociations(); - // Get data from source token instance - $sourceDataStore = $token->getInstance()->getDataStore(); + // Get data from source token instance or empty one if not found + $sourceDataStore = $token->getInstance()?->getDataStore() ?? new DataStore(); // Associate data inputs to message payload foreach ($associations as $association) { From 282cab046a3e45f98f5cdc0ffaed1e6202074a04 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 1 Oct 2025 15:22:19 -0400 Subject: [PATCH 20/27] fix: Ensure non-empty target in callable checks for assignments in MessageEventDefinition --- src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index 03d7ecb7..380537a3 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -169,7 +169,7 @@ private function evaluateAssignments($association, array $data, array &$payload) foreach ($assignments as $assignment) { $from = $assignment->getFrom(); $to = trim($assignment->getTo()?->getBody()); - if (is_callable($from)) { + if (is_callable($from) && !empty($to)) { $payload[] = ['key' => $to, 'value' => $from($data)]; } } From 3d622df748a51c71b33dd42f358a3b264d6fc1b9 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 16:22:12 -0400 Subject: [PATCH 21/27] test: MessageEvent to Parent Catch Mapping with multiple data inputs --- .../MessageEventToParentCatchMapped.bpmn | 223 ++++++++++++++++++ .../MessageEventToParentCatchMapped.json | 25 ++ 2 files changed, 248 insertions(+) create mode 100644 tests/Feature/Patterns/files/MessageEventToParentCatchMapped.bpmn create mode 100644 tests/Feature/Patterns/files/MessageEventToParentCatchMapped.json diff --git a/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.bpmn b/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.bpmn new file mode 100644 index 00000000..e8ccf28d --- /dev/null +++ b/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.bpmn @@ -0,0 +1,223 @@ + + + + + + + + + + + SequenceFlow_0h21x7r + + + Flow_148wtcr + + + SequenceFlow_0h21x7r + Flow_1r89qx6 + + + Flow_1r89qx6 + Flow_148wtcr + + + Flow_0azzp4i + + + + + + + Flow_0azzp4i + + + + + ds_status + + status + status + + + + ds_client + + $data['email'], "login" => $data['login'], "fullName" => $data['fullName']]]]> + client + + + + ds_client + ds_status + + + + + + + Flow_1a3yzli + + + Flow_09pcqxs + + + Flow_1a3yzli + Flow_0ntd3ys + + + + Flow_1r1agfn + Flow_09pcqxs + + + + + + + + Flow_0ntd3ys + Flow_1r1agfn + + + + + + + din_email + ds_email + + + + din_login + + + + + fullName + + + + + din_status + + + + status.name + + + + + status.description + + + + + din_email + din_login + din_status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.json b/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.json new file mode 100644 index 00000000..4d199c2e --- /dev/null +++ b/tests/Feature/Patterns/files/MessageEventToParentCatchMapped.json @@ -0,0 +1,25 @@ +[ + { + "comment": "Message event sent multiple data inputs to parent", + "startEvent": "StartEvent", + "data": { + }, + "result": [ + "Task_B", + "Task_D", + "Task_E", + "Task_F" + ], + "output": { + "client": { + "email": "admin@example.com", + "login": "ADMIN", + "fullName": "admin user" + }, + "status": { + "name": "COMPLETED", + "description": "The task has been completed" + } + } + } +] From e6afbd2b46c8b553ed90febb45973ac9243b1f6a Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 16:22:41 -0400 Subject: [PATCH 22/27] feat: Add DataOutputAssociation handling and extend CatchEventTrait with data output capabilities --- .../Nayra/Bpmn/CatchEventTrait.php | 16 ++++ .../Nayra/Bpmn/DataOutputAssociation.php | 38 ++++++++++ .../Nayra/Bpmn/DataStoreTrait.php | 23 +++--- .../Bpmn/Models/MessageEventDefinition.php | 76 ++++++++++++++----- .../Contracts/Bpmn/CatchEventInterface.php | 13 ++++ .../Contracts/Bpmn/CollectionInterface.php | 3 +- .../Bpmn/DataAssociationInterface.php | 5 ++ .../Bpmn/DataOutputAssociationInterface.php | 1 + .../Contracts/Bpmn/ThrowEventInterface.php | 6 ++ src/ProcessMaker/Nayra/RepositoryTrait.php | 6 ++ .../Nayra/Storage/BpmnDocument.php | 19 ++++- 11 files changed, 173 insertions(+), 33 deletions(-) create mode 100644 src/ProcessMaker/Nayra/Bpmn/DataOutputAssociation.php diff --git a/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php b/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php index e86c6fe1..80646d2a 100644 --- a/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php @@ -30,6 +30,7 @@ protected function initCatchEventTrait() { $this->setProperty(CatchEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS, new Collection); $this->setProperty(CatchEventInterface::BPMN_PROPERTY_PARALLEL_MULTIPLE, false); + $this->setProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION, new Collection); } /** @@ -42,6 +43,21 @@ public function getEventDefinitions() return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS); } + public function getDataOutputAssociations() + { + return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION); + } + + public function getDataOutput() + { + return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT); + } + + public function getDataOutputSet() + { + return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_SET); + } + /** * Register catch events. * diff --git a/src/ProcessMaker/Nayra/Bpmn/DataOutputAssociation.php b/src/ProcessMaker/Nayra/Bpmn/DataOutputAssociation.php new file mode 100644 index 00000000..949e7979 --- /dev/null +++ b/src/ProcessMaker/Nayra/Bpmn/DataOutputAssociation.php @@ -0,0 +1,38 @@ +properties[static::BPMN_PROPERTY_ASSIGNMENT] = new Collection; + } + + public function getSource() + { + return $this->getProperty(static::BPMN_PROPERTY_SOURCES_REF); + } + + public function getTarget() + { + return $this->getProperty(static::BPMN_PROPERTY_TARGET_REF); + } + + public function getTransformation() + { + return $this->getProperty(static::BPMN_PROPERTY_TRANSFORMATION); + } + + public function getAssignments() + { + return $this->getProperty(static::BPMN_PROPERTY_ASSIGNMENT); + } +} diff --git a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php index a00ac2e3..6720854c 100755 --- a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php @@ -113,21 +113,21 @@ public function getDotData($path, $default = null) { $keys = explode('.', $path); $current = $this->data; - + // Navigate through the path foreach ($keys as $key) { // Handle numeric keys for arrays if (is_numeric($key)) { $key = (int) $key; } - + if (!isset($current[$key])) { return $default; } - + $current = $current[$key]; } - + return $current; } @@ -142,31 +142,34 @@ public function getDotData($path, $default = null) public function setDotData($path, $value) { $keys = explode('.', $path); + $firstKey = $keys[0]; $current = &$this->data; - + // Navigate to the parent of the target key for ($i = 0; $i < count($keys) - 1; $i++) { $key = $keys[$i]; - + // Handle numeric keys for arrays if (is_numeric($key)) { $key = (int) $key; } - + if (!isset($current[$key]) || !is_array($current[$key])) { $current[$key] = []; } $current = &$current[$key]; } - + // Set the final value $finalKey = $keys[count($keys) - 1]; if (is_numeric($finalKey)) { $finalKey = (int) $finalKey; } - + $current[$finalKey] = $value; - + // Keep compatibility with putData method (required by PM Core) + $this->putData($firstKey, $this->data[$firstKey]); + return $this; } } diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index 380537a3..67081ebf 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -3,6 +3,9 @@ namespace ProcessMaker\Nayra\Bpmn\Models; use ProcessMaker\Nayra\Bpmn\EventDefinitionTrait; +use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface; use ProcessMaker\Nayra\Contracts\Bpmn\MessageEventDefinitionInterface; @@ -90,47 +93,78 @@ public function assertsRule(EventDefinitionInterface $event, FlowNodeInterface $ public function execute(EventDefinitionInterface $event, FlowNodeInterface $target, ExecutionInstanceInterface $instance = null, TokenInterface $token = null) { $throwEvent = $token->getOwnerElement(); - $this->evaluateMessagePayload($throwEvent, $token, $instance); + $this->executeMessageMapping($throwEvent, $target, $instance, $token); return $this; } /** - * Evaluate the message payload + * Map a message payload from a ThrowEvent through an optional CatchEvent mapping + * into the instance data store. * * @param ThrowEventInterface $throwEvent + * @param CatchEventInterface $catchEvent + * @param ExecutionInstanceInterface $instance * @param TokenInterface $token - * @param ExecutionInstanceInterface $targetInstance + * + * @return void + */ + private function executeMessageMapping(ThrowEventInterface $throwEvent, CatchEventInterface $catchEvent, ExecutionInstanceInterface $instance, TokenInterface $token): void + { + $sourceMaps = $throwEvent->getDataInputAssociations(); + $targetMaps = $catchEvent->getDataOutputAssociations(); + $instanceStore = $instance->getDataStore(); + + // Source of data is the token's instance store if present; otherwise a fresh store. + $sourceStore = $token->getInstance()?->getDataStore() ?? new DataStore(); + + // If target mappings exist we stage into a buffer; otherwise write straight to the instance store. + $bufferStore = !count($targetMaps) ? $instanceStore : new DataStore(); + + // 1) Source mappings: source → buffer/instance + $this->evaluateMessagePayload($sourceMaps, $sourceStore, $bufferStore); + + // 2) Optional target mappings: buffer → instance + if (count($targetMaps)) { + $this->evaluateMessagePayload($targetMaps, $bufferStore, $instanceStore); + } + } + + /** + * Evaluate the message payload + * + * @param CollectionInterface $associations + * @param DataStoreInterface $sourceStore + * @param DataStoreInterface $targetStore + * * @return void */ - private function evaluateMessagePayload(ThrowEventInterface $throwEvent, TokenInterface $token, ExecutionInstanceInterface $targetInstance) + private function evaluateMessagePayload(CollectionInterface $associations, DataStoreInterface $sourceStore, DataStoreInterface $targetStore): void { - // Initialize message payload - $payload = []; - $associations = $throwEvent->getDataInputAssociations(); - // Get data from source token instance or empty one if not found - $sourceDataStore = $token->getInstance()?->getDataStore() ?? new DataStore(); + $assignments = []; - // Associate data inputs to message payload foreach ($associations as $association) { - $data = $sourceDataStore->getData(); $source = $association->getSource(); $target = $association->getTarget(); - // Add reference to source $hasSource = $source && $source->getName(); $hasTarget = $target && $target->getName(); - $data['sourceRef'] = $hasSource ? $sourceDataStore->getDotData($source->getName()) : null; - // Apply transformation - $this->applyTransformation($association, $data, $payload, $hasTarget, $hasSource); + // Base data always starts from full source store + $data = $sourceStore->getData(); + + // Optionally add a direct reference to the source value + if ($hasSource) { + $data['sourceRef'] = $sourceStore->getDotData($source->getName()); + } - // Evaluate assignments - $this->evaluateAssignments($association, $data, $payload); + // Transformation and assignments build up the assignments list + $this->applyTransformation($association, $data, $assignments, $hasTarget, $hasSource); + $this->evaluateAssignments($association, $data, $assignments); } - // Update data into target $instance - $dataStore = $targetInstance->getDataStore(); - foreach ($payload as $load) { - $dataStore->setDotData($load['key'], $load['value']); + + // Flush all assignments into target store + foreach ($assignments as $assignment) { + $targetStore->setDotData($assignment['key'], $assignment['value']); } } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/CatchEventInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/CatchEventInterface.php index 06893e58..54cb676a 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/CatchEventInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/CatchEventInterface.php @@ -16,6 +16,12 @@ interface CatchEventInterface extends EventInterface const EVENT_CATCH_TOKEN_ARRIVES = 'CatchEventTokenArrives'; + const BPMN_PROPERTY_DATA_OUTPUT = 'dataOutput'; + + const BPMN_PROPERTY_DATA_OUTPUT_SET = 'outputSet'; + + const BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION = 'dataOutputAssociation'; + /** * Get EventDefinitions that are triggers expected for a catch Event. * @@ -57,4 +63,11 @@ public function execute(EventDefinitionInterface $event, ExecutionInstanceInterf * @return StateInterface */ public function getActiveState(); + + /** + * Get the data output associations. + * + * @return DataOutputAssociationInterface[] + */ + public function getDataOutputAssociations(); } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/CollectionInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/CollectionInterface.php index 68c11532..7742c805 100755 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/CollectionInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/CollectionInterface.php @@ -3,11 +3,12 @@ namespace ProcessMaker\Nayra\Contracts\Bpmn; use SeekableIterator; +use Countable; /** * CollectionInterface */ -interface CollectionInterface extends SeekableIterator +interface CollectionInterface extends SeekableIterator, Countable { /** * Count the elements of the collection. diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php index 48cc1c6a..d1de4ee3 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php @@ -7,6 +7,11 @@ */ interface DataAssociationInterface extends EntityInterface { + const BPMN_PROPERTY_ASSIGNMENT = 'assignment'; + const BPMN_PROPERTY_SOURCES_REF = 'sourceRef'; + const BPMN_PROPERTY_TARGET_REF = 'targetRef'; + const BPMN_PROPERTY_TRANSFORMATION = 'transformation'; + /** * Get the source of the data association. * diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataOutputAssociationInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataOutputAssociationInterface.php index e0c9ca23..d0edb29c 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataOutputAssociationInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataOutputAssociationInterface.php @@ -7,4 +7,5 @@ */ interface DataOutputAssociationInterface extends DataAssociationInterface { + } diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/ThrowEventInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/ThrowEventInterface.php index cb59e171..3f4aa06a 100644 --- a/src/ProcessMaker/Nayra/Contracts/Bpmn/ThrowEventInterface.php +++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/ThrowEventInterface.php @@ -18,6 +18,12 @@ interface ThrowEventInterface extends EventInterface const EVENT_THROW_TOKEN_CONSUMED = 'ThrowEventTokenConsumed'; + const BPMN_PROPERTY_DATA_INPUT = 'dataInput'; + + const BPMN_PROPERTY_DATA_INPUT_SET = 'inputSet'; + + const BPMN_PROPERTY_DATA_INPUT_ASSOCIATION = 'dataInputAssociation'; + /** * Get Data Inputs for the throw Event. * diff --git a/src/ProcessMaker/Nayra/RepositoryTrait.php b/src/ProcessMaker/Nayra/RepositoryTrait.php index e53cdb79..47c98c70 100644 --- a/src/ProcessMaker/Nayra/RepositoryTrait.php +++ b/src/ProcessMaker/Nayra/RepositoryTrait.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use ProcessMaker\Nayra\Bpmn\Assignment; use ProcessMaker\Nayra\Bpmn\DataInputAssociation; +use ProcessMaker\Nayra\Bpmn\DataOutputAssociation; use ProcessMaker\Nayra\Bpmn\Lane; use ProcessMaker\Nayra\Bpmn\LaneSet; use ProcessMaker\Nayra\Bpmn\Models\Activity; @@ -493,4 +494,9 @@ public function createAssignment() { return new Assignment(); } + + public function createDataOutputAssociation() + { + return new DataOutputAssociation(); + } } diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index 86bc3985..cc368639 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -14,6 +14,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ConditionalEventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataInputAssociationInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataInputInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\DataOutputAssociationInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataOutputInterface; use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EndEventInterface; @@ -54,6 +55,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\StandardLoopCharacteristicsInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TerminateEventDefinitionInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\ThrowEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TimerEventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Engine\EngineInterface; use ProcessMaker\Nayra\Contracts\RepositoryInterface; @@ -119,6 +121,9 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface 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], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT => ['n', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT]], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_SET => ['1', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_SET]], + IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION => ['n', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION]], ], ], 'task' => [ @@ -378,7 +383,7 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION => ['n', [self::BPMN_MODEL, IntermediateThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION]], ], ], - 'dataInputAssociation' => [ + ThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION => [ DataInputAssociationInterface::class, [ DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF]], @@ -414,6 +419,15 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface FormalExpressionInterface::BPMN_PROPERTY_BODY => ['1', self::DOM_ELEMENT_BODY], ], ], + CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION => [ + DataOutputAssociationInterface::class, + [ + DataOutputAssociationInterface::BPMN_PROPERTY_TARGET_REF => ['1', [self::BPMN_MODEL, DataOutputAssociationInterface::BPMN_PROPERTY_TARGET_REF]], + DataOutputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => ['1', [self::BPMN_MODEL, DataOutputAssociationInterface::BPMN_PROPERTY_SOURCES_REF]], + DataOutputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT => ['n', [self::BPMN_MODEL, DataOutputAssociationInterface::BPMN_PROPERTY_ASSIGNMENT]], + DataOutputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => ['1', [self::BPMN_MODEL, DataOutputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION]], + ], + ], 'signalEventDefinition' => [ SignalEventDefinitionInterface::class, [ @@ -455,6 +469,9 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface BoundaryEventInterface::BPMN_PROPERTY_ATTACHED_TO => ['1', [self::BPMN_MODEL, BoundaryEventInterface::BPMN_PROPERTY_ATTACHED_TO_REF]], CatchEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS => ['n', EventDefinitionInterface::class], FlowNodeInterface::BPMN_PROPERTY_OUTGOING => ['n', [self::BPMN_MODEL, FlowNodeInterface::BPMN_PROPERTY_OUTGOING]], + CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT => ['n', [self::BPMN_MODEL, CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT]], + CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_SET => ['1', [self::BPMN_MODEL, CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_SET]], + CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION => ['n', [self::BPMN_MODEL, CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION]], ], ], 'multiInstanceLoopCharacteristics' => [ From fcdba7db2a0476f5b6d8a3a1487f30c32d8cbfef Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 16:30:44 -0400 Subject: [PATCH 23/27] refactor: Remove unused code --- src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php b/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php index 80646d2a..ff0e95aa 100644 --- a/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php +++ b/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php @@ -48,16 +48,6 @@ public function getDataOutputAssociations() return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION); } - public function getDataOutput() - { - return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT); - } - - public function getDataOutputSet() - { - return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_SET); - } - /** * Register catch events. * From 4d966f32ed1fefe69e52f76a82dbdfe7550cb531 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 16:41:25 -0400 Subject: [PATCH 24/27] fix: Improve variable names --- .../Nayra/Bpmn/Models/MessageEventDefinition.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php index 67081ebf..b85cff94 100644 --- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php +++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php @@ -110,22 +110,22 @@ public function execute(EventDefinitionInterface $event, FlowNodeInterface $targ */ private function executeMessageMapping(ThrowEventInterface $throwEvent, CatchEventInterface $catchEvent, ExecutionInstanceInterface $instance, TokenInterface $token): void { - $sourceMaps = $throwEvent->getDataInputAssociations(); - $targetMaps = $catchEvent->getDataOutputAssociations(); - $instanceStore = $instance->getDataStore(); + $sourceMaps = $throwEvent->getDataInputAssociations(); + $targetMaps = $catchEvent->getDataOutputAssociations(); + $targetStore = $instance->getDataStore(); // Source of data is the token's instance store if present; otherwise a fresh store. $sourceStore = $token->getInstance()?->getDataStore() ?? new DataStore(); // If target mappings exist we stage into a buffer; otherwise write straight to the instance store. - $bufferStore = !count($targetMaps) ? $instanceStore : new DataStore(); + $bufferStore = !count($targetMaps) ? $targetStore : new DataStore(); // 1) Source mappings: source → buffer/instance $this->evaluateMessagePayload($sourceMaps, $sourceStore, $bufferStore); // 2) Optional target mappings: buffer → instance if (count($targetMaps)) { - $this->evaluateMessagePayload($targetMaps, $bufferStore, $instanceStore); + $this->evaluateMessagePayload($targetMaps, $bufferStore, $targetStore); } } From 7b77af6c453526f92f3165300ccce31a8916b07d Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 18:18:27 -0400 Subject: [PATCH 25/27] fix: Do not throw exception if node exists but not yet implemented --- src/ProcessMaker/Nayra/Storage/BpmnDocument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index cc368639..def5b254 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -712,7 +712,7 @@ public function getElementInstanceById($id) ? $element->getBpmnElementInstance() : null ); - if ($this->bpmnElements[$id] === null) { + if ($this->bpmnElements[$id] === null && $element === null) { throw new ElementNotFoundException($id); } From 37d3273ef4e83be09615f98c1c4dead543b0c5d8 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 2 Oct 2025 20:08:55 -0400 Subject: [PATCH 26/27] fix: Update condition to check for empty element in BpmnDocument --- src/ProcessMaker/Nayra/Storage/BpmnDocument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index def5b254..b3e2f555 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -712,7 +712,7 @@ public function getElementInstanceById($id) ? $element->getBpmnElementInstance() : null ); - if ($this->bpmnElements[$id] === null && $element === null) { + if ($this->bpmnElements[$id] === null && empty($element)) { throw new ElementNotFoundException($id); } From 62e6510790ba458c6ca0776e27ede77474a48d83 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Fri, 3 Oct 2025 09:21:34 -0400 Subject: [PATCH 27/27] fix: Allow missing target or source references for backward compatibility --- src/ProcessMaker/Nayra/Storage/BpmnDocument.php | 10 ++++++---- src/ProcessMaker/Nayra/Storage/BpmnElement.php | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php index b3e2f555..0425d35a 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php @@ -392,8 +392,8 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => ['1', [self::BPMN_MODEL, DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION]], ], ], - DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => [self::IS_REFERENCE, []], - DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => [self::IS_REFERENCE, []], + DataInputAssociationInterface::BPMN_PROPERTY_TARGET_REF => [self::IS_REFERENCE_OPTIONAL, []], + DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => [self::IS_REFERENCE_OPTIONAL, []], DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => [ FormalExpressionInterface::class, [ @@ -546,6 +546,8 @@ class BpmnDocument extends DOMDocument implements BpmnDocumentInterface const IS_REFERENCE = 'isReference'; + const IS_REFERENCE_OPTIONAL = 'isReferenceOptional'; + const TEXT_PROPERTY = 'textProperty'; const IS_ARRAY = 'isArray'; @@ -703,7 +705,7 @@ public function hasBpmnInstance($id) * * @return \ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface */ - public function getElementInstanceById($id) + public function getElementInstanceById($id, ?bool $isOptional = false) { $this->bpmnElements[$id] = isset($this->bpmnElements[$id]) ? $this->bpmnElements[$id] @@ -712,7 +714,7 @@ public function getElementInstanceById($id) ? $element->getBpmnElementInstance() : null ); - if ($this->bpmnElements[$id] === null && empty($element)) { + if ($this->bpmnElements[$id] === null && empty($element) && !$isOptional) { throw new ElementNotFoundException($id); } diff --git a/src/ProcessMaker/Nayra/Storage/BpmnElement.php b/src/ProcessMaker/Nayra/Storage/BpmnElement.php index c7b71e17..121736d4 100644 --- a/src/ProcessMaker/Nayra/Storage/BpmnElement.php +++ b/src/ProcessMaker/Nayra/Storage/BpmnElement.php @@ -44,9 +44,11 @@ public function getBpmnElementInstance($owner = null) return null; } list($classInterface, $mapProperties) = $map[$this->namespaceURI][$this->localName]; - if ($classInterface === BpmnDocument::IS_REFERENCE) { - $bpmnElement = $this->ownerDocument->getElementInstanceById($this->nodeValue); - $this->bpmn = $bpmnElement; + if ($classInterface === BpmnDocument::IS_REFERENCE || $classInterface === BpmnDocument::IS_REFERENCE_OPTIONAL) { + $bpmnElement = $this->ownerDocument->getElementInstanceById( + $this->nodeValue, + $classInterface === BpmnDocument::IS_REFERENCE_OPTIONAL + ); } elseif ($classInterface === BpmnDocument::TEXT_PROPERTY) { $bpmnElement = $this->nodeValue; $owner->setProperty($this->nodeName, $this->nodeValue);