Completed
Pull Request — 2.10 (#3762)
by Benjamin
21:30
created

DependencyOrderCalculator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Test Coverage

Coverage 98%

Importance

Changes 0
Metric Value
wmc 16
eloc 48
dl 0
loc 136
ccs 49
cts 50
cp 0.98
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A addNode() 0 9 1
A sort() 0 16 3
B visit() 0 45 10
A hasNode() 0 3 1
A addDependency() 0 10 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 152
    public function hasNode(string $hash) : bool
39
    {
40 152
        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 189
    public function addNode(string $hash, $node) : void
49
    {
50 189
        $vertex = new DependencyOrderNode();
51
52 189
        $vertex->hash  = $hash;
53 189
        $vertex->state = self::NOT_VISITED;
54 189
        $vertex->value = $node;
55
56 189
        $this->nodeList[$hash] = $vertex;
57 189
    }
58
59
    /**
60
     * Adds a new dependency (edge) to the graph using their hashes.
61
     */
62 183
    public function addDependency(string $fromHash, string $toHash, int $weight) : void
63
    {
64 183
        $vertex = $this->nodeList[$fromHash];
65 183
        $edge   = new DependencyOrderEdge();
66
67 183
        $edge->from   = $fromHash;
68 183
        $edge->to     = $toHash;
69 183
        $edge->weight = $weight;
70
71 183
        $vertex->dependencyList[$toHash] = $edge;
72 183
    }
73
74
    /**
75
     * Return a valid order list of all current nodes.
76
     * The desired topological sorting is the reverse post order of these searches.
77
     *
78
     * {@internal Highly performance-sensitive method.}
79
     *
80
     * @return array<object>
81
     */
82 226
    public function sort() : array
83
    {
84 226
        foreach ($this->nodeList as $vertex) {
85 189
            if ($vertex->state !== self::NOT_VISITED) {
86 156
                continue;
87
            }
88
89 189
            $this->visit($vertex);
90
        }
91
92 226
        $sortedList = $this->sortedNodeList;
93
94 226
        $this->nodeList       = [];
95 226
        $this->sortedNodeList = [];
96
97 226
        return array_reverse($sortedList);
98
    }
99
100
    /**
101
     * Visit a given node definition for reordering.
102
     *
103
     * {@internal Highly performance-sensitive method.}
104
     */
105 189
    private function visit(DependencyOrderNode $vertex)
106
    {
107 189
        $vertex->state = self::IN_PROGRESS;
108
109 189
        foreach ($vertex->dependencyList as $edge) {
110 183
            $adjacentVertex = $this->nodeList[$edge->to];
111
112 183
            switch ($adjacentVertex->state) {
113 183
                case self::VISITED:
114
                    // Do nothing, since node was already visited
115 179
                    break;
116
117 156
                case self::IN_PROGRESS:
118 129
                    if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
119 129
                        $adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
120
                        // If we have some non-visited dependencies in the in-progress dependency, we
121
                        // need to visit them before adding the node.
122 129
                        foreach ($adjacentVertex->dependencyList as $adjacentEdge) {
123 129
                            $adjacentEdgeVertex = $this->nodeList[$adjacentEdge->to];
124
125 129
                            if ($adjacentEdgeVertex->state !== self::NOT_VISITED) {
126 129
                                continue;
127
                            }
128
129
                            $this->visit($adjacentEdgeVertex);
130
                        }
131
132 129
                        $adjacentVertex->state = self::VISITED;
133
134 129
                        $this->sortedNodeList[] = $adjacentVertex->value;
135
                    }
136 129
                    break;
137
138 156
                case self::NOT_VISITED:
139 181
                    $this->visit($adjacentVertex);
140
            }
141
        }
142
143 189
        if ($vertex->state === self::VISITED) {
144 129
            return;
145
        }
146
147 189
        $vertex->state = self::VISITED;
148
149 189
        $this->sortedNodeList[] = $vertex->value;
150 189
    }
151
}
152