Completed
Push — master ( 16daf9...8a3ba8 )
by Mike
03:45
created

Graph::breadthFirstSearch()   C

Complexity

Conditions 8
Paths 20

Size

Total Lines 50
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 23
c 0
b 0
f 0
nc 20
nop 2
dl 0
loc 50
rs 6.3636
1
<?php
2
3
namespace Sugarcrm\UpgradeSpec\Version\Graph;
4
5
use Sugarcrm\UpgradeSpec\Version\Version;
6
7
class Graph
8
{
9
    /**
10
     * @var AdjacencyList
11
     */
12
    private $graph;
13
14
    /**
15
     * @var array
16
     */
17
    private $visited = [];
18
19
    /**
20
     * Graph constructor.
21
     *
22
     * @param AdjacencyList $graph
23
     */
24
    public function __construct(AdjacencyList $graph)
25
    {
26
        $this->graph = $graph;
27
    }
28
29
    /**
30
     * @param Version $from
31
     * @param Version $to
32
     *
33
     * @return array
34
     */
35
    public function getPath(Version $from, Version $to)
36
    {
37
        $destinations = $this->getDestinationVertexes($to);
38
        foreach ($this->getOriginVertexes($from) as $origin) {
39
            foreach ($destinations as $destination) {
40
                if ($path = $this->breadthFirstSearch($origin, $destination)) {
41
                    return $this->getRealHops($path);
42
                }
43
            }
44
        }
45
46
        return [];
47
    }
48
49
    /**
50
     * @param $path
51
     *
52
     * @return array
53
     */
54
    private function getRealHops($path)
55
    {
56
        $pairs = [];
57
58
        $first = $path[0];
59
        foreach (range(1, count($path) - 1) as $index) {
60
            $second = $path[$index];
61
            if ($first->isChildOf($second)) {
62
                $first = $second;
63
                continue;
64
            }
65
66
            $pairs[] = [$first, $second];
67
68
            $first = $second;
69
        }
70
71
        return $pairs;
72
    }
73
74
    /**
75
     * Finds least number of hops (edges) between 2 nodes (vertices)
76
     * "breadth first" search
77
     *
78
     * @param Version $from
79
     * @param Version $to
80
     *
81
     * @return array
82
     */
83
    private function breadthFirstSearch(Version $from, Version $to)
84
    {
85
86
        // mark all nodes as unvisited
87
        foreach ($this->graph as $vertex => $adj) {
88
            $this->visited[$vertex] = false;
89
        }
90
91
        // create an empty queue
92
        $queue = new \SplQueue();
93
94
        // enqueue the origin vertex and mark as visited
95
        $queue->enqueue($from);
96
        $this->visited[(string) $from] = true;
97
98
        // this is used to track the path back from each node
99
        $path = [];
100
        $path[(string) $from] = new \SplDoublyLinkedList();
101
        $path[(string) $from]->setIteratorMode(
102
            \SplDoublyLinkedList::IT_MODE_FIFO | \SplDoublyLinkedList::IT_MODE_KEEP
103
        );
104
105
        $path[(string) $from]->push($from);
106
107
        // while queue is not empty and destination not found
108
        while (!$queue->isEmpty() && !$queue->bottom()->isEqualTo($to, false)) {
109
            $t = $queue->dequeue();
110
111
            if (!empty($this->graph[(string) $t])) {
112
                // for each adjacent neighbor
113
                foreach ($this->graph[(string) $t] as $vertex) {
114
                    if (!$this->visited[(string) $vertex]) {
115
                        // if not yet visited, enqueue vertex and mark as visited
116
                        $queue->enqueue($vertex);
117
                        $this->visited[(string) $vertex] = true;
118
119
                        // add vertex to current path
120
                        $path[(string) $vertex] = clone $path[(string) $t];
121
                        $path[(string) $vertex]->push($vertex);
122
                    }
123
                }
124
            }
125
        }
126
127
        if (!isset($path[(string) $to])) {
128
            return [];
129
        }
130
131
        return iterator_to_array($path[(string) $to]);
132
    }
133
134
    /**
135
     * @param Version $from
136
     *
137
     * @return OrderedList
0 ignored issues
show
Documentation introduced by
Should the return type not be \Sugarcrm\UpgradeSpec\Version\OrderedList?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
138
     */
139
    private function getOriginVertexes(Version $from)
140
    {
141
        $aliases = $from->getAliases();
142
        while ($from->isMinor()) {
143
            $from = $from->getParent();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $from. This often makes code more readable.
Loading history...
144
            $aliases = $aliases->merge($from->getAliases());
145
        }
146
147
        return $aliases->filter(function (Version $version) {
148
            return isset($this->graph[$version]);
149
        });
150
    }
151
152
    /**
153
     * @param Version $to
154
     *
155
     * @return OrderedList
0 ignored issues
show
Documentation introduced by
Should the return type not be \Sugarcrm\UpgradeSpec\Version\OrderedList?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
156
     */
157
    private function getDestinationVertexes(Version $to)
158
    {
159
        return $to->getAliases()->filter(function (Version $version) {
160
            return isset($this->graph[$version]);
161
        });
162
    }
163
}
164