Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.15% covered (success)
96.15%
25 / 26
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WorkflowDagParser
96.00% covered (success)
96.00%
24 / 25
75.00% covered (warning)
75.00%
3 / 4
18
0.00% covered (danger)
0.00%
0 / 1
 parseGraphNodes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 extractNodesFromJson
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 findTriggerNodeId
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 enqueueNextNodes
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Automation\Application\Service\Dag;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\Automation\Domain\Model\Workflow;
12
13/**
14 * Workflow DAG Canvas Graph Parser and Node Traversal Utility.
15 *
16 * Deserializes visual Drawflow graphs into structured node arrays and
17 * determines branch connectivity sequences.
18 *
19 * @package App\Modules\Automation\Application\Service\Dag
20 */
21final readonly class WorkflowDagParser
22{
23    public const string TRIGGER_PREFIX = 'trigger_';
24    public const string TRIGGER_NAME = 'trigger';
25
26    /**
27     * Parses graph nodes from workflow definition.
28     *
29     * @param Workflow $workflow Target workflow.
30     * @return array<string, array<string, mixed>> Indexed nodes array.
31     */
32    public function parseGraphNodes(Workflow $workflow): array
33    {
34        $raw = $workflow->compiledFlow ?? $workflow->graphData;
35        if ($raw === null || trim($raw) === '') {
36            return [];
37        }
38
39        return $this->extractNodesFromJson((string) $raw);
40    }
41
42    /**
43     * Decodes and extracts nodes array from raw JSON.
44     *
45     * @param string $raw JSON payload.
46     * @return array<string, array<string, mixed>>
47     */
48    public function extractNodesFromJson(string $raw): array
49    {
50        $decoded = json_decode($raw, true);
51        if (!is_array($decoded)) {
52            return [];
53        }
54
55        $homeData = $decoded['drawflow']['Home']['data'] ?? null;
56        if (is_array($homeData)) {
57            return $homeData;
58        }
59
60        return isset($decoded['nodes']) && is_array($decoded['nodes']) ? $decoded['nodes'] : [];
61    }
62
63    /**
64     * Finds the entrypoint trigger node in the graph.
65     *
66     * @param array<string, array<string, mixed>> $nodes Indexed nodes array.
67     * @return string|null ID of trigger node or first node.
68     */
69    public function findTriggerNodeId(array $nodes): ?string
70    {
71        foreach ($nodes as $id => $node) {
72            $name = (string) ($node['name'] ?? '');
73            if (str_starts_with($name, self::TRIGGER_PREFIX) || $name === self::TRIGGER_NAME) {
74                return (string) $id;
75            }
76        }
77
78        $keys = array_keys($nodes);
79        return isset($keys[0]) ? (string) $keys[0] : null;
80    }
81
82    /**
83     * Enqueues unvisited connected nodes to the traversal queue.
84     *
85     * @param array<string, mixed> $node           Current node definition.
86     * @param string               $branchToFollow Output port branch key.
87     * @param array<string, bool>  $visited        Visited nodes map.
88     * @param list<string>         $queue          Active traversal queue reference.
89     */
90    public function enqueueNextNodes(
91        array  $node,
92        string $branchToFollow,
93        array  $visited,
94        array  &$queue
95    ): void {
96        $outputs = $node['outputs'] ?? [];
97        $connections = $outputs[$branchToFollow]['connections'] ?? null;
98        if (!is_array($connections)) {
99            return;
100        }
101
102        foreach ($connections as $conn) {
103            $targetNode = (string) ($conn['node'] ?? '');
104            if ($targetNode !== '' && !isset($visited[$targetNode])) {
105                $queue[] = $targetNode;
106            }
107        }
108    }
109}