TermHierarchyRepository   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 256
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 8
ccs 84
cts 84
cp 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A addParent() 0 4 1
A unsetParents() 0 4 1
A supportsCommonTableExpressionQuery() 0 14 4
A getRecursiveRetriever() 0 8 2
A getCacheKey() 0 4 1
A getAncestry() 0 11 2
A getDescent() 0 11 2
A prepareVertices() 0 20 4
A getAncestryGraph() 0 24 4
A getDescentGraph() 0 24 4
A getAncestryPaths() 0 12 2
A getDescentPaths() 0 12 2
1
<?php
2
/**
3
 * The repository of all hierarchies of terms
4
 */
5
namespace Rocket\Taxonomy\Repositories;
6
7
use CentralDesktop\Graph\Graph\DirectedGraph;
8
use CentralDesktop\Graph\Vertex;
9
use Illuminate\Support\Facades\DB;
10
use Rocket\Taxonomy\Model\Hierarchy;
11
use Rocket\Taxonomy\Utils\CommonTableExpressionQuery;
12
use Rocket\Taxonomy\Utils\PathResolver;
13
use Rocket\Taxonomy\Utils\RecursiveQuery;
14
15
/**
16
 * Create paths from a term all the way to all the parents.
17
 *
18
 * Everything is calculated upside down so that the DFS search for all paths is easy
19
 */
20
class TermHierarchyRepository implements TermHierarchyRepositoryInterface
21
{
22
    /**
23
     * @var array<Vertex> all Vertices (Current and parents)
24
     */
25
    protected $vertices;
26
27
    /**
28
     * @var \Illuminate\Cache\Repository The cache to store the terms in.
29
     */
30
    protected $cache;
31
32
    /**
33
     * Create a Repository
34
     *
35
     * @param \Illuminate\Cache\Repository $cache
36
     */
37 192
    public function __construct(\Illuminate\Cache\Repository $cache)
38
    {
39 192
        $this->cache = $cache;
40 192
    }
41
42
    /**
43
     * Add a parent to this term.
44
     *
45
     * @param int $term_id The term
46
     * @param int $parent_id The parent term
47
     * @return bool
48
     */
49 111
    public function addParent($term_id, $parent_id)
50
    {
51 111
        return Hierarchy::insert(['term_id' => $term_id, 'parent_id' => $parent_id]);
52
    }
53
54
    /**
55
     * Remove this term's parents
56
     *
57
     * @param int $term_id The term
58
     * @return bool
59
     */
60 6
    public function unsetParents($term_id)
61
    {
62 6
        return Hierarchy::where('term_id', $term_id)->delete();
63
    }
64
65
    /**
66
     * Tests if the driver supports Common Table Expression.
67
     *
68
     * If it doesn't we'll fall back to a manually recursive query.
69
     *
70
     * @return bool
71
     */
72 9
    protected function supportsCommonTableExpressionQuery()
73
    {
74 9
        $driver = DB::connection()->getDriverName();
75
76 9
        if ($driver == 'mysql') {
77 3
            return true;
78 3
        }
79
80 6
        if ($driver == 'sqlite' && \SQLite3::version()['versionNumber'] >= 3008003) {
81 3
            return true;
82
        }
83
84 3
        return false;
85
    }
86
87
    /**
88
     * Get the retriever to get the Term's Hierarchy
89
     *
90
     * @return \Rocket\Taxonomy\Utils\RecursiveQueryInterface
91
     */
92 9
    protected function getRecursiveRetriever()
93
    {
94 9
        if ($this->supportsCommonTableExpressionQuery()) {
95 6
            return new CommonTableExpressionQuery();
96
        }
97
98 3
        return new RecursiveQuery();
99
    }
100
101
    /**
102
     * Get the hierarchy cache key
103
     *
104
     * @param string $direction The hierarchy's direction
105
     * @param int $id The term's id
106
     * @return string
107
     */
108 12
    protected function getCacheKey($direction, $id)
109
    {
110 12
        return "Rocket::Taxonomy::TermHierarchy::$direction::$id";
111
    }
112
113
    /**
114
     * Get all parents recursively
115
     *
116
     * @param int $id The term's id
117
     * @return \Illuminate\Support\Collection
118
     */
119 12
    public function getAncestry($id)
120
    {
121 12
        $key = $this->getCacheKey('ancestry', $id);
122 12
        if ($results = $this->cache->get($key)) {
123 4
            return $results;
124
        }
125
126 9
        $this->cache->add($key, $results = $this->getRecursiveRetriever()->getAncestry($id), 2);
127
128 9
        return $results;
129
    }
130
131
    /**
132
     * Get all childs recursively.
133
     *
134
     * @param int $id The term's id
135
     * @return \Illuminate\Support\Collection
136
     */
137 12
    public function getDescent($id)
138
    {
139 12
        $key = $this->getCacheKey('descent', $id);
140 12
        if ($results = $this->cache->get($key)) {
141 4
            return $results;
142
        }
143
144 9
        $this->cache->add($key, $results = $this->getRecursiveRetriever()->getDescent($id), 2);
145
146 9
        return $results;
147
    }
148
149
    /**
150
     * Prepare the vertices to create the graph.
151
     *
152
     * @param \Illuminate\Support\Collection $data The list of vertices that were retrieved
153
     * @return array<Vertex>
154
     */
155 9
    protected function prepareVertices($data)
156
    {
157 9
        $vertices = [];
158 9
        foreach ($data as $content) {
159
            // identifiers must be strings or SplObjectStorage::contains fails
160
            // seems to impact only PHP 5.6
161 9
            $content->term_id = "$content->term_id";
162 9
            $content->parent_id = "$content->parent_id";
163
164 9
            if (!array_key_exists($content->term_id, $vertices)) {
165 9
                $vertices[$content->term_id] = new Vertex($content->term_id);
166 9
            }
167
168 9
            if (!array_key_exists($content->parent_id, $vertices)) {
169 9
                $vertices[$content->parent_id] = new Vertex($content->parent_id);
170 9
            }
171 9
        }
172
173 9
        return $vertices;
174
    }
175
176
    /**
177
     * Get all parents recursively
178
     *
179
     * @param int $id The term we want the ancestry from.
180
     * @return array Vertex, DirectedGraph
181
     */
182 12
    public function getAncestryGraph($id)
183
    {
184 12
        $data = $this->getAncestry($id);
185
186 12
        if (count($data) == 0) {
187 3
            return [null, null];
188
        }
189
190
        // Create Vertices
191 9
        $this->vertices = $this->prepareVertices($data);
192
193
        // Create Graph
194 9
        $graph = new DirectedGraph();
195 9
        foreach ($this->vertices as $vertex) {
196 9
            $graph->add_vertex($vertex);
197 9
        }
198
199
        // Create Relations
200 9
        foreach ($data as $content) {
201 9
            $graph->create_edge($this->vertices[$content->parent_id], $this->vertices[$content->term_id]);
202 9
        }
203
204 9
        return [$this->vertices[$id], $graph];
205
    }
206
207
    /**
208
     * Get all childs recursively
209
     *
210
     * @param int $id The term we want the descent from.
211
     * @return array Vertex, DirectedGraph
212
     */
213 12
    public function getDescentGraph($id)
214
    {
215 12
        $data = $this->getDescent($id);
216
217 12
        if (count($data) == 0) {
218 3
            return [null, null];
219
        }
220
221
        // Create Vertices
222 9
        $this->vertices = $this->prepareVertices($data);
223
224
        // Create Graph
225 9
        $graph = new DirectedGraph();
226 9
        foreach ($this->vertices as $vertex) {
227 9
            $graph->add_vertex($vertex);
228 9
        }
229
230
        // Create Relations
231 9
        foreach ($data as $content) {
232 9
            $graph->create_edge($this->vertices[$content->term_id], $this->vertices[$content->parent_id]);
233 9
        }
234
235 9
        return [$this->vertices[$id], $graph];
236
    }
237
238
    /**
239
     * Get all the possible paths from this term
240
     *
241
     * @param int $id The term we want the ancestry from.
242
     * @return array<array<int>>
243
     */
244 12
    public function getAncestryPaths($id)
245
    {
246 12
        list($start_vertex, $graph) = $this->getAncestryGraph($id);
247
248 12
        if (!$graph) {
249 3
            return [];
250
        }
251
252 9
        $resolver = new PathResolver($graph);
253
254 9
        return $resolver->resolvePaths($start_vertex);
255
    }
256
257
    /**
258
     * Get all the possible paths from this term
259
     *
260
     * @param int $id The term we want the descent from.
261
     * @return array<array<int>>
262
     */
263 12
    public function getDescentPaths($id)
264
    {
265 12
        list($start_vertex, $graph) = $this->getDescentGraph($id);
266
267 12
        if (!$graph) {
268 3
            return [];
269
        }
270
271 9
        $resolver = new PathResolver($graph);
272
273 9
        return $resolver->resolvePaths($start_vertex);
274
    }
275
}
276