Failed Conditions
Pull Request — 2.10 (#3762)
by Benjamin
09:16
created

DependencyOrderCalculator::addDependency()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
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 104
    public function hasNode(string $hash) : bool
39
    {
40 104
        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 145
    public function addNode(string $hash, $node) : void
49
    {
50 145
        $vertex = new DependencyOrderNode();
51
52 145
        $vertex->hash  = $hash;
53 145
        $vertex->state = self::NOT_VISITED;
54 145
        $vertex->value = $node;
55
56 145
        $this->nodeList[$hash] = $vertex;
57 145
    }
58
59
    /**
60
     * Adds a new dependency (edge) to the graph using their hashes.
61
     */
62 133
    public function addDependency(string $fromHash, string $toHash) : void
63
    {
64 133
        $vertex = $this->nodeList[$fromHash];
65 133
        $edge   = new DependencyOrderEdge();
66
67 133
        $edge->from = $fromHash;
68 133
        $edge->to   = $toHash;
69
70 133
        $vertex->dependencyList[$toHash] = $edge;
71 133
    }
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 184
    public function sort() : array
82
    {
83 184
        foreach ($this->nodeList as $vertex) {
84 145
            if ($vertex->state !== self::NOT_VISITED) {
85 104
                continue;
86
            }
87
88 145
            $this->visit($vertex);
89
        }
90
91 184
        $sortedList = $this->sortedNodeList;
92
93 184
        $this->nodeList       = [];
94 184
        $this->sortedNodeList = [];
95
96 184
        return array_reverse($sortedList);
97
    }
98
99
    /**
100
     * Visit a given node definition for reordering.
101
     *
102
     * {@internal Highly performance-sensitive method.}
103
     */
104 145
    private function visit(DependencyOrderNode $vertex)
105
    {
106 145
        $vertex->state = self::IN_PROGRESS;
107
108 145
        foreach ($vertex->dependencyList as $edge) {
109 133
            $adjacentVertex = $this->nodeList[$edge->to];
110
111 133
            switch ($adjacentVertex->state) {
112 133
                case self::VISITED:
113 104
                case self::IN_PROGRESS:
114
                    // Do nothing, since node was already visited or is
115
                    // currently visited
116 133
                    break;
117
118 104
                case self::NOT_VISITED:
119 129
                    $this->visit($adjacentVertex);
120
            }
121
        }
122
123 145
        if ($vertex->state === self::VISITED) {
124
            return;
125
        }
126
127 145
        $vertex->state = self::VISITED;
128
129 145
        $this->sortedNodeList[] = $vertex->value;
130 145
    }
131
}
132