Failed Conditions
Push — master ( 3e08da...9d75d1 )
by Bernhard
06:13
created

KeyValueStoreDiscovery   C

Complexity

Total Complexity 72

Size/Duplication

Total Lines 529
Duplicated Lines 12.48 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 94.14%

Importance

Changes 17
Bugs 4 Features 9
Metric Value
wmc 72
c 17
b 4
f 9
lcom 1
cbo 10
dl 66
loc 529
ccs 225
cts 239
cp 0.9414
rs 5.5668

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A addBindingType() 0 18 2
B removeBindingType() 0 29 4
A removeBindingTypes() 0 10 1
A hasBindingType() 0 6 1
A getBindingType() 0 16 3
A hasBindingTypes() 0 4 1
A getBindingTypes() 0 20 4
B addBinding() 0 27 4
A hasBinding() 0 4 1
B getBinding() 0 22 5
B findBindings() 0 22 4
A getBindings() 0 12 2
A removeAllBindings() 0 12 2
B removeBindingsWithTypeName() 0 22 4
B removeBindingsWithTypeNameThatMatch() 25 25 5
A hasAnyBinding() 0 4 1
A hasBindingsThatMatch() 0 14 4
A hasBindingsWithTypeName() 0 10 2
B hasBindingsWithTypeNameThatMatch() 0 20 5
B loadAllBindings() 0 17 5
A loadBindingsForKey() 0 8 2
B removeBinding() 28 28 5
A removeBindingsThatMatch() 13 20 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like KeyValueStoreDiscovery 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 KeyValueStoreDiscovery, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/discovery package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Discovery;
13
14
use Puli\Discovery\Api\Binding\Binding;
15
use Puli\Discovery\Api\Binding\Initializer\BindingInitializer;
16
use Puli\Discovery\Api\Binding\NoSuchBindingException;
17
use Puli\Discovery\Api\Type\BindingType;
18
use Puli\Discovery\Api\Type\DuplicateTypeException;
19
use Puli\Discovery\Api\Type\NoSuchTypeException;
20
use Rhumsaa\Uuid\Uuid;
21
use RuntimeException;
22
use Webmozart\Assert\Assert;
23
use Webmozart\Expression\Expression;
24
use Webmozart\KeyValueStore\Api\KeyValueStore;
25
26
/**
27
 * A resource discovery that stores the bindings in a key-value store.
28
 *
29
 * @since  1.0
30
 *
31
 * @author Bernhard Schussek <[email protected]>
32
 */
33
class KeyValueStoreDiscovery extends AbstractEditableDiscovery
34
{
35
    /**
36
     * @var KeyValueStore
37
     */
38
    private $store;
39
40
    /**
41
     * Stores the integer key that will be used when adding the next binding type.
42
     *
43
     * Synchronized with the entry "::nextKey" in the store.
44
     *
45
     * @var int
46
     */
47
    private $nextKey;
48
49
    /**
50
     * Stores an integer "key" for each binding type name.
51
     *
52
     * Contains each key only once.
53
     *
54
     * Synchronized with the entry "::keysByTypeName" in the store.
55
     *
56
     * @var int[]
57
     */
58
    private $keysByTypeName;
59
60
    /**
61
     * Stores the corresponding keys for each binding UUID.
62
     *
63
     * Potentially contains keys zero or multiple times.
64
     *
65
     * Synchronized with the entry "::keysByUuid" in the store.
66
     *
67
     * @var int[]
68
     */
69
    private $keysByUuid;
70
71
    /**
72
     * Stores the binding type for each key.
73
     *
74
     * Synchronized with the entries "t:<key>" in the store.
75
     *
76
     * @var BindingType[]
77
     */
78
    private $typesByKey = array();
79
80
    /**
81
     * Stores the bindings for each key.
82
     *
83
     * Synchronized with the entries "b:<key>" in the store.
84
     *
85
     * @var Binding[][]
86
     */
87
    private $bindingsByKey = array();
88
89
    /**
90
     * Creates a new resource discovery.
91
     *
92
     * @param KeyValueStore        $store        The key-value store used to
93
     *                                           store the bindings and the
94
     *                                           binding types.
95
     * @param BindingInitializer[] $initializers The binding initializers to
96
     *                                           apply to newly created or
97
     *                                           unserialized bindings.
98
     */
99 96
    public function __construct(KeyValueStore $store, array $initializers = array())
100
    {
101 96
        parent::__construct($initializers);
102
103 96
        $this->store = $store;
104 96
        $this->keysByTypeName = $store->get('::keysByTypeName', array());
105 96
        $this->keysByUuid = $store->get('::keysByUuid', array());
106 96
        $this->nextKey = $store->get('::nextKey', 0);
107 96
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112 62
    public function addBindingType(BindingType $type)
113
    {
114 62
        if (isset($this->keysByTypeName[$type->getName()])) {
115 2
            throw DuplicateTypeException::forTypeName($type->getName());
116
        }
117
118 62
        $key = $this->nextKey++;
119
120 62
        $this->keysByTypeName[$type->getName()] = $key;
121
122
        // Use integer keys to reduce storage space
123
        // (compared to fully-qualified class names)
124 62
        $this->typesByKey[$key] = $type;
125
126 62
        $this->store->set('::keysByTypeName', $this->keysByTypeName);
127 62
        $this->store->set('::nextKey', $this->nextKey);
128 62
        $this->store->set('t:'.$key, $type);
129 62
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 8
    public function removeBindingType($typeName)
135
    {
136 8
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
137
138 6
        if (!isset($this->keysByTypeName[$typeName])) {
139 2
            return;
140
        }
141
142 4
        $key = $this->keysByTypeName[$typeName];
143
144 4
        if (!isset($this->bindingsByKey[$key])) {
145
            // no initialize, since we're removing this anyway
146 2
            $this->loadBindingsForKey($key, false);
147 2
        }
148
149
        // Remove all binding UUIDs for this binding type
150 4
        foreach ($this->bindingsByKey[$key] as $binding) {
151 2
            unset($this->keysByUuid[$binding->getUuid()->toString()]);
152 4
        }
153
154 4
        unset($this->keysByTypeName[$typeName]);
155 4
        unset($this->typesByKey[$key]);
156 4
        unset($this->bindingsByKey[$key]);
157
158 4
        $this->store->remove('t:'.$key);
159 4
        $this->store->remove('b:'.$key);
160 4
        $this->store->set('::keysByTypeName', $this->keysByTypeName);
161 4
        $this->store->set('::keysByUuid', $this->keysByUuid);
162 4
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167 4
    public function removeBindingTypes()
168
    {
169 4
        $this->keysByTypeName = array();
170 4
        $this->keysByUuid = array();
171 4
        $this->typesByKey = array();
172 4
        $this->bindingsByKey = array();
173 4
        $this->nextKey = 0;
174
175 4
        $this->store->clear();
176 4
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181 10
    public function hasBindingType($typeName)
182
    {
183 10
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
184
185 8
        return isset($this->keysByTypeName[$typeName]);
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 48
    public function getBindingType($typeName)
192
    {
193 48
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
194
195 46
        if (!isset($this->keysByTypeName[$typeName])) {
196 2
            throw NoSuchTypeException::forTypeName($typeName);
197
        }
198
199 44
        $key = $this->keysByTypeName[$typeName];
200
201 44
        if (!isset($this->typesByKey[$key])) {
202 16
            $this->typesByKey[$key] = $this->store->get('t:'.$key);
203 16
        }
204
205 44
        return $this->typesByKey[$key];
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function hasBindingTypes()
212
    {
213
        return count($this->keysByTypeName) > 0;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 6
    public function getBindingTypes()
220
    {
221 6
        $keysToFetch = array();
222
223 6
        foreach ($this->keysByTypeName as $key) {
224 4
            if (!isset($this->typesByKey[$key])) {
225 2
                $keysToFetch[] = 't:'.$key;
226 2
            }
227 6
        }
228
229 6
        $types = $this->store->getMultiple($keysToFetch);
230
231 6
        foreach ($types as $prefixedKey => $type) {
232 2
            $this->typesByKey[substr($prefixedKey, 2)] = $type;
233 6
        }
234
235 6
        ksort($this->typesByKey);
236
237 6
        return $this->typesByKey;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 40
    public function addBinding(Binding $binding)
244
    {
245 40
        $typeName = $binding->getTypeName();
246
247 40
        if (!isset($this->keysByTypeName[$typeName])) {
248 2
            throw NoSuchTypeException::forTypeName($typeName);
249
        }
250
251 38
        if (isset($this->keysByUuid[$binding->getUuid()->toString()])) {
252
            // Ignore duplicates
253 2
            return;
254
        }
255
256 38
        $key = $this->keysByTypeName[$typeName];
257
258 38
        if (!isset($this->bindingsByKey[$key])) {
259 38
            $this->loadBindingsForKey($key);
260 38
        }
261
262 38
        $this->initializeBinding($binding);
263
264 36
        $this->keysByUuid[$binding->getUuid()->toString()] = $key;
265 36
        $this->bindingsByKey[$key][] = $binding;
266
267 36
        $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
268 36
        $this->store->set('::keysByUuid', $this->keysByUuid);
269 36
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 4 View Code Duplication
    public function removeBinding(Uuid $uuid)
275
    {
276 4
        $uuidString = $uuid->toString();
277
278 4
        if (!isset($this->keysByUuid[$uuidString])) {
279 2
            return;
280
        }
281
282 2
        $key = $this->keysByUuid[$uuidString];
283
284 2
        if (!isset($this->bindingsByKey[$key])) {
285
            $this->loadBindingsForKey($key);
286
        }
287
288 2
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
289 2
            if ($binding->getUuid()->equals($uuid)) {
290 2
                unset($this->bindingsByKey[$key][$i]);
291 2
            }
292 2
        }
293
294
        // Reindex array
295 2
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
296
297 2
        unset($this->keysByUuid[$uuidString]);
298
299 2
        $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
300 2
        $this->store->set('::keysByUuid', $this->keysByUuid);
301 2
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 16
    public function hasBinding(Uuid $uuid)
307
    {
308 16
        return isset($this->keysByUuid[$uuid->toString()]);
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     */
314 4
    public function getBinding(Uuid $uuid)
315
    {
316 4
        if (!isset($this->keysByUuid[$uuid->toString()])) {
317 2
            throw NoSuchBindingException::forUuid($uuid);
318
        }
319
320 2
        $key = $this->keysByUuid[$uuid->toString()];
321
322 2
        if (!isset($this->bindingsByKey[$key])) {
323 1
            $this->loadBindingsForKey($key);
324 1
        }
325
326 2
        foreach ($this->bindingsByKey[$key] as $binding) {
327 2
            if ($binding->getUuid()->equals($uuid)) {
328 2
                return $binding;
329
            }
330 2
        }
331
332
        // This does not happen except if someone plays with the key-value store
333
        // contents (or there's a bug here..)
334
        throw new RuntimeException('The discovery is corrupt. Please rebuild it.');
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340 24
    public function findBindings($typeName, Expression $expr = null)
341
    {
342 24
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
343
344 22
        if (!isset($this->keysByTypeName[$typeName])) {
345 2
            return array();
346
        }
347
348 20
        $key = $this->keysByTypeName[$typeName];
349
350 20
        if (!isset($this->bindingsByKey[$key])) {
351 12
            $this->loadBindingsForKey($key);
352 12
        }
353
354 20
        $bindings = $this->bindingsByKey[$key];
355
356 20
        if (null !== $expr) {
357 2
            $bindings = $this->filterBindings($bindings, $expr);
358 2
        }
359
360 20
        return $bindings;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366 34
    public function getBindings()
367
    {
368 34
        $this->loadAllBindings();
369
370 34
        $bindings = array();
371
372 34
        foreach ($this->bindingsByKey as $bindingsForKey) {
373 24
            $bindings = array_merge($bindings, $bindingsForKey);
374 34
        }
375
376 34
        return $bindings;
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382 4
    protected function removeAllBindings()
383
    {
384 4
        $this->bindingsByKey = array();
385 4
        $this->keysByUuid = array();
386
387
        // Iterate $keysByTypeName which does not contain duplicate keys
388 4
        foreach ($this->keysByTypeName as $key) {
389 2
            $this->store->remove('b:'.$key);
390 4
        }
391
392 4
        $this->store->remove('::keysByUuid');
393 4
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398 2
    protected function removeBindingsThatMatch(Expression $expr)
399
    {
400 2
        $this->loadAllBindings();
401
402 2 View Code Duplication
        foreach ($this->bindingsByKey as $key => $bindingsForKey) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403 2
            foreach ($bindingsForKey as $i => $binding) {
404 2
                if ($expr->evaluate($binding)) {
405 2
                    unset($this->bindingsByKey[$key][$i]);
406 2
                    unset($this->keysByUuid[$binding->getUuid()->toString()]);
407 2
                }
408 2
            }
409
410
            // Reindex array
411 2
            $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
412
413 2
            $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
414 2
        }
415
416 2
        $this->store->set('::keysByUuid', $this->keysByUuid);
417 2
    }
418
419
    /**
420
     * {@inheritdoc}
421
     */
422 6
    protected function removeBindingsWithTypeName($typeName)
423
    {
424 6
        if (!isset($this->keysByTypeName[$typeName])) {
425 2
            return;
426
        }
427
428 4
        $key = $this->keysByTypeName[$typeName];
429
430 4
        if (!isset($this->bindingsByKey[$key])) {
431
            // no initialize, since we're removing this anyway
432 2
            $this->loadBindingsForKey($key, false);
433 2
        }
434
435 4
        foreach ($this->bindingsByKey[$key] as $binding) {
436 2
            unset($this->keysByUuid[$binding->getUuid()->toString()]);
437 4
        }
438
439 4
        unset($this->bindingsByKey[$key]);
440
441 4
        $this->store->remove('b:'.$key);
442 4
        $this->store->set('::keysByUuid', $this->keysByUuid);
443 4
    }
444
445
    /**
446
     * {@inheritdoc}
447
     */
448 6 View Code Duplication
    protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr)
449
    {
450 6
        if (!isset($this->keysByTypeName[$typeName])) {
451 2
            return;
452
        }
453
454 4
        $key = $this->keysByTypeName[$typeName];
455
456 4
        if (!isset($this->bindingsByKey[$key])) {
457 2
            $this->loadBindingsForKey($key);
458 2
        }
459
460 4
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
461 2
            if ($expr->evaluate($binding)) {
462 2
                unset($this->bindingsByKey[$key][$i]);
463 2
                unset($this->keysByUuid[$binding->getUuid()->toString()]);
464 2
            }
465 4
        }
466
467
        // Reindex array
468 4
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
469
470 4
        $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
471 4
        $this->store->set('::keysByUuid', $this->keysByUuid);
472 4
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477 4
    protected function hasAnyBinding()
478
    {
479 4
        return count($this->keysByUuid) > 0;
480
    }
481
482
    /**
483
     * {@inheritdoc}
484
     */
485
    protected function hasBindingsThatMatch(Expression $expr)
486
    {
487
        $this->loadAllBindings();
488
489
        foreach ($this->bindingsByKey as $bindingsForKey) {
490
            foreach ($bindingsForKey as $binding) {
491
                if ($expr->evaluate($binding)) {
492
                    return true;
493
                }
494
            }
495
        }
496
497
        return false;
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503 8
    protected function hasBindingsWithTypeName($typeName)
504
    {
505 8
        if (!isset($this->keysByTypeName[$typeName])) {
506 4
            return false;
507
        }
508
509 4
        $key = $this->keysByTypeName[$typeName];
510
511 4
        return false !== array_search($key, $this->keysByUuid, true);
512
    }
513
514 4
    protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr)
515
    {
516 4
        if (!$this->hasBindingsWithTypeName($typeName)) {
517 2
            return false;
518
        }
519
520 2
        $key = $this->keysByTypeName[$typeName];
521
522 2
        if (!isset($this->bindingsByKey[$key])) {
523 1
            $this->loadBindingsForKey($key);
524 1
        }
525
526 2
        foreach ($this->bindingsByKey[$key] as $binding) {
527 2
            if ($expr->evaluate($binding)) {
528 2
                return true;
529
            }
530 2
        }
531
532 2
        return false;
533
    }
534
535 34
    private function loadAllBindings()
536
    {
537 34
        $keysToFetch = array();
538
539 34
        foreach ($this->keysByTypeName as $key) {
540 24
            if (!isset($this->bindingsByKey[$key])) {
541 5
                $keysToFetch[] = 'b:'.$key;
542 5
            }
543 34
        }
544
545 34
        $fetchedBindings = $this->store->getMultiple($keysToFetch);
546
547 34
        foreach ($fetchedBindings as $key => $bindingsForKey) {
548 5
            $this->bindingsByKey[$key] = $bindingsForKey ?: array();
549 5
            $this->initializeBindings($this->bindingsByKey[$key]);
550 34
        }
551 34
    }
552
553 44
    private function loadBindingsForKey($key, $initialize = true)
554
    {
555 44
        $this->bindingsByKey[$key] = $this->store->get('b:'.$key, array());
556
557 44
        if ($initialize) {
558 40
            $this->initializeBindings($this->bindingsByKey[$key]);
559 40
        }
560 44
    }
561
}
562