Taxonomy   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 391
rs 8.8
c 0
b 0
f 0
ccs 131
cts 131
cp 1
wmc 45
lcom 3
cbo 7

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A isTranslatable() 0 8 2
A getLanguage() 0 12 3
A vocabulary() 0 8 2
A vocabularies() 0 4 1
A getTerm() 0 11 3
A uncacheTerm() 0 8 2
A getAncestryPaths() 0 4 1
A getDescentPaths() 0 4 1
A getAncestryGraph() 0 4 1
A getDescentGraph() 0 4 1
A addParent() 0 6 1
A addParents() 0 8 2
A testCanAddParents() 0 18 6
A unsetParents() 0 4 1
A getTermsForVocabulary() 0 19 3
A searchTerm() 0 21 3
A getTermId() 0 35 5
A getTermIds() 0 17 5

How to fix   Complexity   

Complex Class

Complex classes like Taxonomy often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Taxonomy, and based on these observations, apply Extract Interface, too.

1
<?php namespace Rocket\Taxonomy;
2
3
use Illuminate\Contracts\Cache\Repository as CacheRepository;
4
use Rocket\Taxonomy\Model\Hierarchy;
5
use Rocket\Taxonomy\Model\TermContainer;
6
use Rocket\Taxonomy\Model\TermData;
7
use Rocket\Taxonomy\Model\Vocabulary;
8
use Rocket\Taxonomy\Repositories\TermHierarchyRepositoryInterface as TermHieraRep;
9
use Rocket\Taxonomy\Repositories\TermRepositoryInterface as TermRep;
10
use Rocket\Translation\Support\Laravel5\Facade as I18N;
11
12
/**
13
 * The Taxonomy base class handles the cache and the creation/modification of terms
14
 */
15
class Taxonomy implements TaxonomyInterface
16
{
17
    /**
18
     * @var array Terms internal cache
19
     */
20
    public $terms = [];
21
22
    /**
23
     * @var array List of vocabularies by ID
24
     */
25
    protected $vocabularyById = [];
26
27
    /**
28
     * @var array List of vocabularies by Name
29
     */
30
    protected $vocabularyByName = [];
31
32
    /**
33
     * @var CacheRepository The repository to cache Terms
34
     */
35
    protected $cache;
36
37
    /**
38
     * @var TermRep The repository to load Terms
39
     */
40
    protected $termRepository;
41
42
    /**
43
     * @var TermHieraRep The repository to handle terms hierarchies
44
     */
45
    protected $termHierarchyRepository;
46
47
    /**
48
     * Initialize the Taxonomy system, Loads all existing vocabularies
49
     *
50
     * @param CacheRepository $cache The cache repository
51
     * @param TermRep $termRepository The term retrieval repository
52
     * @param TermHieraRep $termHierarchyRepository The term hierarchy repository
53
     */
54
    public function __construct(CacheRepository $cache, TermRep $termRepository, TermHieraRep $termHierarchyRepository)
55
    {
56
        $this->termRepository = $termRepository;
57
        $this->termHierarchyRepository = $termHierarchyRepository;
58
        $this->cache = $cache;
59 192
60
        // Get the list of vocabularies
61 192
        $vocs = $cache->remember('Rocket::Taxonomy::Vocabularies', 60 * 24 * 7, function () {
62 192
            return Vocabulary::all();
63 192
        });
64
65
        // Initialize the search for terms
66
        foreach ($vocs as $v) {
67 192
            $this->vocabularyByName[$v->machine_name] = $this->vocabularyById[$v->id] = $v;
68 192
        }
69
    }
70
71 192
    /**
72 186
     * Is this vocabulary translatable ?
73 192
     *
74 192
     * @param int|string $vid
75
     * @return bool
76
     */
77
    public function isTranslatable($vid)
78
    {
79
        if (!is_numeric($vid)) {
80
            $vid = $this->vocabulary($vid);
81
        }
82 180
83
        return $this->vocabularyById[$vid]->isTranslatable();
84 180
    }
85 3
86 3
    /**
87
     * Get the internal language for the vocabulary
88 180
     *
89
     * This will return the language_id if the vocabulary is translated or 1 if it's not
90
     *
91
     * @param int|string $vocabulary_id
92
     * @param int $language_id
93
     * @return int|null
94
     */
95
    public function getLanguage($vocabulary_id, $language_id = null)
96
    {
97
        if (!$this->isTranslatable($vocabulary_id)) {
98
            return 1;
99
        }
100 177
101
        if ($language_id === null) {
102 177
            return I18N::getCurrentId();
103 33
        }
104
105
        return $language_id;
106 150
    }
107 150
108
    /**
109
     * Get a vocabulary by name or ID
110 150
     *
111
     *     Taxonomy::vocabulary(1);
112
     *     returns 'tags'
113
     *
114
     *     Taxonomy::vocabulary('tags');
115
     *     returns 1
116
     *
117
     * @param int|string $key
118
     * @return mixed
119
     */
120
    public function vocabulary($key)
121
    {
122
        if (is_numeric($key)) {
123
            return $this->vocabularyById[$key]->machine_name;
124
        }
125 186
126
        return $this->vocabularyByName[$key]->id;
127 186
    }
128 3
129
    /**
130
     * Get all vocabularies with keys as id's
131 186
     *
132
     * @return array
133
     */
134
    public function vocabularies()
135
    {
136
        return $this->vocabularyById;
137
    }
138
139 6
    /**
140
     * Get a term with all translations
141 6
     *
142
     * @param int $term_id The term's ID
143
     * @param bool $from_cache Should we take this term from cache or request a fresh one ?
144
     * @return Term
145
     */
146
    public function getTerm($term_id, $from_cache = true)
147
    {
148
        if ($from_cache && array_key_exists($term_id, $this->terms)) {
149
            return $this->terms[$term_id];
150
        }
151 39
152
        $data = $this->termRepository->getTerm($term_id);
153 39
        $this->terms[$term_id] = $data;
154 3
155
        return $data;
156
    }
157 39
158 39
    /**
159
     * Remove a term from the cache
160 39
     *
161
     * @param int $term_id
162
     * @return bool
163
     */
164
    public function uncacheTerm($term_id)
165
    {
166
        if (array_key_exists($term_id, $this->terms)) {
167
            unset($this->terms[$term_id]);
168
        }
169 174
170
        return $this->termRepository->uncacheTerm($term_id);
171 174
    }
172 3
173 3
    /**
174
     * Get all paths for a term
175 174
     *
176
     * @param int $term_id
177
     * @return array<array<int>>
178
     */
179
    public function getAncestryPaths($term_id)
180
    {
181
        return $this->termHierarchyRepository->getAncestryPaths($term_id);
182
    }
183
184 12
    /**
185
     * Get all paths for a term
186 12
     *
187
     * @param int $term_id
188
     * @return array<array<int>>
189
     */
190
    public function getDescentPaths($term_id)
191
    {
192
        return $this->termHierarchyRepository->getDescentPaths($term_id);
193
    }
194
195 12
    /**
196
     * Get the complete graph
197 12
     * @param int $term_id
198
     * @return array
199
     */
200
    public function getAncestryGraph($term_id)
201
    {
202
        return $this->termHierarchyRepository->getAncestryGraph($term_id);
203
    }
204
205 3
    /**
206
     * Get the complete graph
207 3
     * @param int $term_id
208
     * @return array
209
     */
210
    public function getDescentGraph($term_id)
211
    {
212
        return $this->termHierarchyRepository->getDescentGraph($term_id);
213
    }
214
215 3
    /**
216
     * Add one parent to a term
217 3
     *
218
     * @param int $term_id
219
     * @param int $parent_id
220
     */
221
    public function addParent($term_id, $parent_id)
222
    {
223
        $this->testCanAddParents($term_id, 1);
224
225
        $this->termHierarchyRepository->addParent($term_id, $parent_id);
226 111
    }
227
228 111
    /**
229
     * Add a list of parents to a term
230 108
     *
231 108
     * @param int $term_id
232
     * @param array<integer> $parent_ids
233
     */
234
    public function addParents($term_id, array $parent_ids)
235
    {
236
        $this->testCanAddParents($term_id, count($parent_ids));
237
238
        foreach ($parent_ids as $id) {
239 6
            $this->termHierarchyRepository->addParent($term_id, $id);
240
        }
241 6
    }
242
243 6
    /**
244 6
     * Test if the term can have more parents
245 6
     *
246 6
     * @param int $term_id
247
     * @param int $count
248
     * @throws \RuntimeException
249
     */
250
    protected function testCanAddParents($term_id, $count)
251
    {
252
        $vocabulary = (new Vocabulary())->getTable();
253
        $term = (new TermContainer())->getTable();
254
        $v = Vocabulary::select('hierarchy', 'name')
255 114
            ->where("$term.id", $term_id)
256
            ->join($term, 'vocabulary_id', '=', "$vocabulary.id")
257 114
            ->first();
258 114
259 114
        if ($v->hierarchy == 0) {
260 114
            throw new \RuntimeException("Cannot add a parent in vocabulary '$v->name'");
261 114
        }
262 114
263
        if (($v->hierarchy == 1 && Hierarchy::where('term_id', $term_id)->count() > 0)
264 114
            || ($v->hierarchy == 1 && $count > 1)) {
265 3
            throw new \RuntimeException("Cannot have more than one parent in vocabulary '$v->name'");
266
        }
267
    }
268 111
269 111
    /**
270 3
     * Remove the parents form this term
271
     *
272 111
     * @param int $term_id
273
     * @return bool
274
     */
275
    public function unsetParents($term_id)
276
    {
277
        return $this->termHierarchyRepository->unsetParents($term_id);
278
    }
279
280 6
    /**
281
     * Get all the terms of a vocabulary
282 6
     *
283
     * @param int $vocabulary_id
284
     * @return array
285
     */
286
    public function getTermsForVocabulary($vocabulary_id)
287
    {
288
        return $this->cache->remember(
289
            'Rocket::Taxonomy::Terms::' . $vocabulary_id,
290
            60,
291 3
            function () use ($vocabulary_id) {
292
                $terms = TermContainer::where('vocabulary_id', $vocabulary_id)->get(['id']);
293 3
294 3
                $results = [];
295 3
                if (!empty($terms)) {
296 3
                    foreach ($terms as $term) {
297 3
                        $results[] = $term->id;
298
                    }
299 3
                }
300 3
301 3
                return $results;
302 3
            }
303 3
        );
304 3
    }
305
306 3
    /**
307
     * Search a specific term, if it doesn't exist, returns false
308 3
     *
309
     * @param  string $term
310
     * @param  int $vocabulary_id
311
     * @param  int $language_id
312
     * @param  array $exclude
313
     * @return int|null
314
     */
315
    public function searchTerm($term, $vocabulary_id, $language_id = null, $exclude = [])
316
    {
317
        $language_id = $this->getLanguage($vocabulary_id, $language_id);
318
319
        $term = trim($term);
320 174
        if ($term == '') {
321
            return;
322 174
        }
323
324 174
        $query = TermData::select('taxonomy_terms.id')
325 174
            ->join('taxonomy_terms', 'taxonomy_terms.id', '=', 'taxonomy_terms_data.term_id')
326 3
            ->where('taxonomy_terms.vocabulary_id', $vocabulary_id)
327
            ->where('taxonomy_terms_data.language_id', $language_id)
328
            ->where('taxonomy_terms_data.title', $term);
329 174
330 174
        if (count($exclude)) {
331 174
            $query->whereNotIn('taxonomy_terms.id', $exclude);
332 174
        }
333 174
334
        return $query->value('id');
335 174
    }
336 3
337 3
    /**
338
     * Returns the id of a term, if it doesn't exist, creates it.
339 174
     *
340
     * @param  string $title
341
     * @param  int $vocabulary_id
342
     * @param  int $language_id
343
     * @param  int $type
344
     * @return bool|int
345
     */
346
    public function getTermId($title, $vocabulary_id, $language_id = null, $type = 0)
347
    {
348
        $title = trim($title);
349
        if ($title == '') {
350
            return false;
351 177
        }
352
353 177
        if (!is_numeric($vocabulary_id)) {
354 177
            $vocabulary_id = $this->vocabulary($vocabulary_id);
355 3
        }
356
357
        $language_id = $this->getLanguage($vocabulary_id, $language_id);
358 174
359 15
        $search = $this->searchTerm($title, $vocabulary_id, $language_id);
360 15
        if ($search !== null) {
361
            return $search;
362 174
        }
363
364 174
        // Add term
365 174
        $term = new TermContainer(['vocabulary_id' => $vocabulary_id]);
366 21
367
        if ($type !== 0) {
368
            $term->type = $type;
369
        }
370 174
        $term->save();
371
372 174
        // Add translation
373 6
        $translation = [
374 6
            'language_id' => $language_id,
375 174
            'title' => $title,
376
        ];
377
        $term->translations()->save(new TermData($translation));
378
379 174
        return $term->id;
380 174
    }
381 174
382 174
    /**
383
     * Adds one or more tags and returns an array of id's
384 174
     *
385
     * @param array $taxonomies
386
     * @return array
387
     */
388
    public function getTermIds($taxonomies)
389
    {
390
        $tags = [];
391
        foreach ($taxonomies as $voc => $terms) {
392
            $vocabulary_id = $this->vocabulary($voc);
393 30
            $exploded = is_array($terms) ? $terms : explode(',', $terms);
394
395 30
            foreach ($exploded as $term) {
396 30
                $result = $this->getTermId($term, $vocabulary_id);
397 30
                if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
398 30
                    $tags[] = $result;
399
                }
400 30
            }
401 30
        }
402 30
403 30
        return $tags;
404 30
    }
405
}
406