Failed Conditions
Pull Request — 3.0.x (#3987)
by Grégoire
33:07 queued 11s
created

DependencyOrderCalculator::visit()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.0131

Importance

Changes 0
Metric Value
eloc 13
c 0
b 0
f 0
dl 0
loc 26
ccs 13
cts 14
cp 0.9286
rs 9.2222
cc 6
nc 10
nop 1
crap 6.0131
1
<?php
2
3
namespace Doctrine\DBAL\Internal;
4
5
use function array_reverse;
6
7
/**
8
 * DependencyOrderCalculator implements topological sorting, which is an ordering
9
 * algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by
10
 * using a depth-first searching (DFS) to traverse the graph built in memory.
11
 * This algorithm have a linear running time based on nodes (V) and dependency
12
 * between the nodes (E), resulting in a computational complexity of O(V + E).
13
 */
14
final class DependencyOrderCalculator
15
{
16
    public const NOT_VISITED = 0;
17
    public const IN_PROGRESS = 1;
18
    public const VISITED     = 2;
19
20
    /**
21
     * Matrix of nodes (aka. vertex).
22
     * Keys are provided hashes and values are the node definition objects.
23
     *
24
     * @var array<string,DependencyOrderNode>
25
     */
26
    private $nodeList = [];
27
28
    /**
29
     * Volatile variable holding calculated nodes during sorting process.
30
     *
31
     * @var array<object>
32
     */
33
    private $sortedNodeList = [];
34
35
    /**
36
     * Checks for node (vertex) existence in graph.
37
     */
38 22
    public function hasNode(string $hash) : bool
39
    {
40 22
        return isset($this->nodeList[$hash]);
41
    }
42
43
    /**
44
     * Adds a new node (vertex) to the graph, assigning its hash and value.
45
     *
46
     * @param object $node
47
     */
48 110
    public function addNode(string $hash, $node) : void
49
    {
50 110
        $vertex = new DependencyOrderNode();
51
52 110
        $vertex->hash  = $hash;
53 110
        $vertex->state = self::NOT_VISITED;
54 110
        $vertex->value = $node;
55
56 110
        $this->nodeList[$hash] = $vertex;
57 110
    }
58
59
    /**
60
     * Adds a new dependency (edge) to the graph using their hashes.
61
     */
62 44
    public function addDependency(string $fromHash, string $toHash) : void
63
    {
64 44
        $vertex = $this->nodeList[$fromHash];
65 44
        $edge   = new DependencyOrderEdge();
66
67 44
        $edge->from = $fromHash;
68 44
        $edge->to   = $toHash;
69
70 44
        $vertex->dependencyList[$toHash] = $edge;
71 44
    }
72
73
    /**
74
     * Return a valid order list of all current nodes.
75
     * The desired topological sorting is the reverse post order of these searches.
76
     *
77
     * {@internal Highly performance-sensitive method.}
78
     *
79
     * @return array<object>
80
     */
81 141
    public function sort() : array
82
    {
83 141
        foreach ($this->nodeList as $vertex) {
84 110
            if ($vertex->state !== self::NOT_VISITED) {
85 22
                continue;
86
            }
87
88 110
            $this->visit($vertex);
89
        }
90
91 141
        $sortedList = $this->sortedNodeList;
92
93 141
        $this->nodeList       = [];
94 141
        $this->sortedNodeList = [];
95
96 141
        return array_reverse($sortedList);
97
    }
98
99
    /**
100
     * Visit a given node definition for reordering.
101
     *
102
     * {@internal Highly performance-sensitive method.}
103
     */
104 110
    private function visit(DependencyOrderNode $vertex) : void
105
    {
106 110
        $vertex->state = self::IN_PROGRESS;
107
108 110
        foreach ($vertex->dependencyList as $edge) {
109 44
            $adjacentVertex = $this->nodeList[$edge->to];
110
111 44
            switch ($adjacentVertex->state) {
112 44
                case self::VISITED:
113 22
                case self::IN_PROGRESS:
114
                    // Do nothing, since node was already visited or is
115
                    // currently visited
116 44
                    break;
117
118 22
                case self::NOT_VISITED:
119 22
                    $this->visit($adjacentVertex);
120
            }
121
        }
122
123 110
        if ($vertex->state === self::VISITED) {
124
            return;
125
        }
126
127 110
        $vertex->state = self::VISITED;
128
129 110
        $this->sortedNodeList[] = $vertex->value;
130 110
    }
131
}
132