Failed Conditions
Pull Request — 2.6 (#7180)
by Ben
10:13
created

CommitOrderCalculator   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Test Coverage

Coverage 96.08%

Importance

Changes 0
Metric Value
dl 0
loc 158
ccs 49
cts 51
cp 0.9608
rs 10
c 0
b 0
f 0
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
A hasNode() 0 3 1
A addNode() 0 10 1
C visit() 0 30 8
A addDependency() 0 20 4
A sort() 0 16 3
1
<?php
2
3
/*
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the MIT license. For more information, see
18
 * <http://www.doctrine-project.org>.
19
 */
20
21
namespace Doctrine\ORM\Internal;
22
23
/**
24
 * CommitOrderCalculator implements topological sorting, which is an ordering
25
 * algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by
26
 * using a depth-first searching (DFS) to traverse the graph built in memory.
27
 * This algorithm have a linear running time based on nodes (V) and dependency
28
 * between the nodes (E), resulting in a computational complexity of O(V + E).
29
 *
30
 * @since  2.0
31
 * @author Guilherme Blanco <[email protected]>
32
 * @author Roman Borschel <[email protected]>
33
 */
34
class CommitOrderCalculator
35
{
36
    const NOT_VISITED = 0;
37
    const IN_PROGRESS = 1;
38
    const VISITED     = 2;
39
40
    /**
41
     * Matrix of nodes (aka. vertex).
42
     * Keys are provided hashes and values are the node definition objects.
43
     *
44
     * The node state definition contains the following properties:
45
     *
46
     * - <b>state</b> (integer)
47
     * Whether the node is NOT_VISITED or IN_PROGRESS
48
     *
49
     * - <b>value</b> (object)
50
     * Actual node value
51
     *
52
     * - <b>dependencyList</b> (array<string>)
53
     * Map of node dependencies defined as hashes.
54
     *
55
     * @var array<\stdClass>
56
     */
57
    private $nodeList = [];
58
59
    /**
60
     * Volatile variable holding calculated nodes during sorting process.
61
     *
62
     * @var array
63
     */
64
    private $sortedNodeList = [];
65
66
    /**
67
     * Checks for node (vertex) existence in graph.
68
     *
69
     * @param string $hash
70
     *
71
     * @return boolean
72
     */
73 1075
    public function hasNode($hash)
74
    {
75 1075
        return isset($this->nodeList[$hash]);
76
    }
77
78
    /**
79
     * Adds a new node (vertex) to the graph, assigning its hash and value.
80
     *
81
     * @param string $hash
82
     * @param object $node
83
     *
84
     * @return void
85
     */
86 1078
    public function addNode($hash, $node)
87
    {
88 1078
        $vertex = new \stdClass();
89
90 1078
        $vertex->hash           = $hash;
91 1078
        $vertex->state          = self::NOT_VISITED;
92 1078
        $vertex->value          = $node;
93 1078
        $vertex->dependencyList = [];
94
95 1078
        $this->nodeList[$hash] = $vertex;
96 1078
    }
97
98
    /**
99
     * Adds a new dependency (edge) to the graph using their hashes.
100
     *
101
     * @param string  $fromHash
102
     * @param string  $toHash
103
     * @param integer $weight
104
     *
105
     * @return void
106
     */
107 887
    public function addDependency($fromHash, $toHash, $weight)
108
    {
109 887
        if (isset($this->nodeList[$toHash]->dependencyList[$fromHash])) {
110 236
            $cycleWeight = $this->nodeList[$toHash]->dependencyList[$fromHash]->weight;
111 236
            if ($cycleWeight > $weight) {
112 77
                return;
113
            }
114 233
            if ($cycleWeight < $weight) {
115 212
                unset($this->nodeList[$toHash]->dependencyList[$fromHash]);
116
            }
117
        }
118
119 887
        $vertex = $this->nodeList[$fromHash];
120 887
        $edge   = new \stdClass();
121
122 887
        $edge->from   = $fromHash;
123 887
        $edge->to     = $toHash;
124 887
        $edge->weight = $weight;
125
126 887
        $vertex->dependencyList[$toHash] = $edge;
127 887
    }
128
129
    /**
130
     * Return a valid order list of all current nodes.
131
     * The desired topological sorting is the reverse post order of these searches.
132
     *
133
     * {@internal Highly performance-sensitive method.}
134
     *
135
     * @return array
136
     */
137 1078
    public function sort()
138
    {
139 1078
        foreach ($this->nodeList as $vertex) {
140 1078
            if ($vertex->state !== self::NOT_VISITED) {
141 549
                continue;
142
            }
143
144 1078
            $this->visit($vertex);
145
        }
146
147 1078
        $sortedList = $this->sortedNodeList;
148
149 1078
        $this->nodeList       = [];
150 1078
        $this->sortedNodeList = [];
151
152 1078
        return array_reverse($sortedList);
153
    }
154
155
    /**
156
     * Visit a given node definition for reordering.
157
     *
158
     * {@internal Highly performance-sensitive method.}
159
     *
160
     * @param \stdClass $vertex
161
     */
162 1078
    private function visit($vertex)
163
    {
164 1078
        $vertex->state = self::IN_PROGRESS;
165
166 1078
        foreach ($vertex->dependencyList as $edge) {
167 887
            $adjacentVertex = $this->nodeList[$edge->to];
168
169 887
            switch ($adjacentVertex->state) {
170 887
                case self::VISITED:
171
                    // Do nothing, since node was already visited
172 768
                    break;
173
174 589
                case self::IN_PROGRESS:
175 279
                    if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
176 279
                        $adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
177
                        $adjacentVertex->state = self::VISITED;
178
179
                        $this->sortedNodeList[] = $adjacentVertex->value;
180
                    }
181 279
                    break;
182
183 549
                case self::NOT_VISITED:
184 887
                    $this->visit($adjacentVertex);
185
            }
186
        }
187
188 1078
        if ($vertex->state !== self::VISITED) {
189 1078
            $vertex->state = self::VISITED;
190
191 1078
            $this->sortedNodeList[] = $vertex->value;
192
        }
193 1078
    }
194
}
195