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:
diff --git a/src/ProcessMaker/Nayra/Bpmn/Assignment.php b/src/ProcessMaker/Nayra/Bpmn/Assignment.php
new file mode 100644
index 00000000..f4c99d2a
--- /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|callable
+ */
+ public function getTo()
+ {
+ return $this->getProperty(self::BPMN_PROPERTY_TO);
+ }
+}
diff --git a/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php b/src/ProcessMaker/Nayra/Bpmn/CatchEventTrait.php
index e86c6fe1..ff0e95aa 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,11 @@ public function getEventDefinitions()
return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_EVENT_DEFINITIONS);
}
+ public function getDataOutputAssociations()
+ {
+ return $this->getProperty(CatchEventInterface::BPMN_PROPERTY_DATA_OUTPUT_ASSOCIATION);
+ }
+
/**
* Register catch events.
*
diff --git a/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.php
new file mode 100644
index 00000000..a95b8c54
--- /dev/null
+++ b/src/ProcessMaker/Nayra/Bpmn/DataInputAssociation.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/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 47eb66dd..6720854c 100755
--- a/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php
+++ b/src/ProcessMaker/Nayra/Bpmn/DataStoreTrait.php
@@ -100,4 +100,76 @@ 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.
+ *
+ * @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);
+ $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/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);
}
/**
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..b85cff94 100644
--- a/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php
+++ b/src/ProcessMaker/Nayra/Bpmn/Models/MessageEventDefinition.php
@@ -3,11 +3,15 @@
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;
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 +92,123 @@ public function assertsRule(EventDefinitionInterface $event, FlowNodeInterface $
*/
public function execute(EventDefinitionInterface $event, FlowNodeInterface $target, ExecutionInstanceInterface $instance = null, TokenInterface $token = null)
{
+ $throwEvent = $token->getOwnerElement();
+ $this->executeMessageMapping($throwEvent, $target, $instance, $token);
return $this;
}
+ /**
+ * 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
+ *
+ * @return void
+ */
+ private function executeMessageMapping(ThrowEventInterface $throwEvent, CatchEventInterface $catchEvent, ExecutionInstanceInterface $instance, TokenInterface $token): void
+ {
+ $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) ? $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, $targetStore);
+ }
+ }
+
+ /**
+ * Evaluate the message payload
+ *
+ * @param CollectionInterface $associations
+ * @param DataStoreInterface $sourceStore
+ * @param DataStoreInterface $targetStore
+ *
+ * @return void
+ */
+ private function evaluateMessagePayload(CollectionInterface $associations, DataStoreInterface $sourceStore, DataStoreInterface $targetStore): void
+ {
+ $assignments = [];
+
+ foreach ($associations as $association) {
+ $source = $association->getSource();
+ $target = $association->getTarget();
+
+ $hasSource = $source && $source->getName();
+ $hasTarget = $target && $target->getName();
+
+ // 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());
+ }
+
+ // Transformation and assignments build up the assignments list
+ $this->applyTransformation($association, $data, $assignments, $hasTarget, $hasSource);
+ $this->evaluateAssignments($association, $data, $assignments);
+ }
+
+ // Flush all assignments into target store
+ foreach ($assignments as $assignment) {
+ $targetStore->setDotData($assignment['key'], $assignment['value']);
+ }
+ }
+
+ /**
+ * 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) && !empty($to)) {
+ $payload[] = ['key' => $to, 'value' => $from($data)];
+ }
+ }
+ }
+
/**
* Check if the $eventDefinition should be catch
*
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/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 b12f5c69..d1de4ee3 100644
--- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php
+++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataAssociationInterface.php
@@ -7,12 +7,17 @@
*/
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.
*
- * @return ItemAwareElementInterface[]
+ * @return ItemAwareElementInterface
*/
- public function getSources();
+ public function getSource();
/**
* Get the target of the data association.
@@ -31,5 +36,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..97cca901 100644
--- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php
+++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataInputAssociationInterface.php
@@ -7,4 +7,8 @@
*/
interface DataInputAssociationInterface extends DataAssociationInterface
{
+ const BPMN_PROPERTY_ASSIGNMENT = 'assignment';
+ const BPMN_PROPERTY_SOURCES_REF = 'sourceRef';
+ const BPMN_PROPERTY_TARGET_REF = 'targetRef';
+ const BPMN_PROPERTY_TRANSFORMATION = 'transformation';
}
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/DataStoreInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php
index 3fef523f..0f491500 100755
--- a/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php
+++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/DataStoreInterface.php
@@ -76,4 +76,24 @@ 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);
+
+ /**
+ * 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/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';
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);
}
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 e56bac84..47c98c70 100644
--- a/src/ProcessMaker/Nayra/RepositoryTrait.php
+++ b/src/ProcessMaker/Nayra/RepositoryTrait.php
@@ -3,6 +3,9 @@
namespace ProcessMaker\Nayra;
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;
@@ -481,4 +484,19 @@ public function createStandardLoopCharacteristics()
{
return new StandardLoopCharacteristics();
}
+
+ public function createDataInputAssociation()
+ {
+ return new DataInputAssociation();
+ }
+
+ 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 70c7d29e..0425d35a 100644
--- a/src/ProcessMaker/Nayra/Storage/BpmnDocument.php
+++ b/src/ProcessMaker/Nayra/Storage/BpmnDocument.php
@@ -6,12 +6,15 @@
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\DataOutputAssociationInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\DataOutputInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\EndEventInterface;
@@ -52,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;
@@ -117,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' => [
@@ -371,6 +378,54 @@ 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]],
+ ],
+ ],
+ ThrowEventInterface::BPMN_PROPERTY_DATA_INPUT_ASSOCIATION => [
+ 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_OPTIONAL, []],
+ DataInputAssociationInterface::BPMN_PROPERTY_SOURCES_REF => [self::IS_REFERENCE_OPTIONAL, []],
+ DataInputAssociationInterface::BPMN_PROPERTY_TRANSFORMATION => [
+ FormalExpressionInterface::class,
+ [
+ FormalExpressionInterface::BPMN_PROPERTY_BODY => ['1', self::DOM_ELEMENT_BODY],
+ ],
+ ],
+ 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],
+ ],
+ ],
+ 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' => [
@@ -414,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' => [
@@ -488,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';
@@ -645,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]
@@ -654,7 +714,7 @@ public function getElementInstanceById($id)
? $element->getBpmnElementInstance()
: null
);
- if ($this->bpmnElements[$id] === null) {
+ 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);
diff --git a/tests/Feature/Patterns/files/MessageEventToParent.bpmn b/tests/Feature/Patterns/files/MessageEventToParent.bpmn
new file mode 100644
index 00000000..c3e777ff
--- /dev/null
+++ b/tests/Feature/Patterns/files/MessageEventToParent.bpmn
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+ 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_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/MessageEventToParent.json b/tests/Feature/Patterns/files/MessageEventToParent.json
new file mode 100644
index 00000000..19a447ad
--- /dev/null
+++ b/tests/Feature/Patterns/files/MessageEventToParent.json
@@ -0,0 +1,23 @@
+[
+ {
+ "comment": "Message event sent multiple data inputs to parent",
+ "startEvent": "StartEvent",
+ "data": {
+ },
+ "result": [
+ "Task_B",
+ "Task_D",
+ "Task_E",
+ "Task_F"
+ ],
+ "output": {
+ "email": "admin@example.com",
+ "login": "ADMIN",
+ "fullName": "admin user",
+ "status": {
+ "name": "COMPLETED",
+ "description": "The task has been completed"
+ }
+ }
+ }
+]
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"
+ }
+ }
+ }
+]
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'));
+ }
}