KeyValueStoreDiscovery   C
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 446
Duplicated Lines 3.81 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 19
Bugs 4 Features 10
Metric Value
c 19
b 4
f 10
dl 17
loc 446
wmc 64
lcom 1
cbo 9
ccs 165
cts 165
cp 1
rs 5.8364

23 Methods

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

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\Type\BindingType;
17
use Puli\Discovery\Api\Type\DuplicateTypeException;
18
use Puli\Discovery\Api\Type\NoSuchTypeException;
19
use Webmozart\Assert\Assert;
20
use Webmozart\Expression\Expr;
21
use Webmozart\Expression\Expression;
22
use Webmozart\KeyValueStore\Api\KeyValueStore;
23
24
/**
25
 * A discovery that stores the bindings in a key-value store.
26
 *
27
 * @since  1.0
28
 *
29
 * @author Bernhard Schussek <[email protected]>
30
 */
31
class KeyValueStoreDiscovery extends AbstractEditableDiscovery
32
{
33
    /**
34
     * @var KeyValueStore
35
     */
36
    private $store;
37
38
    /**
39
     * Stores the integer key that will be used when adding the next binding type.
40
     *
41
     * Synchronized with the entry "::nextKey" in the store.
42
     *
43
     * @var int
44
     */
45
    private $nextKey;
46
47
    /**
48
     * Stores an integer "key" for each binding type name.
49
     *
50
     * Contains each key only once.
51
     *
52
     * Synchronized with the entry "::keysByTypeName" in the store.
53
     *
54
     * @var int[]
55
     */
56
    private $keysByTypeName;
57
58
    /**
59
     * Stores the binding type for each key.
60
     *
61
     * Synchronized with the entries "t:<key>" in the store.
62
     *
63
     * @var BindingType[]
64
     */
65
    private $typesByKey = array();
66
67
    /**
68
     * Stores the bindings for each key.
69
     *
70
     * Synchronized with the entries "b:<key>" in the store.
71
     *
72
     * @var Binding[][]
73
     */
74
    private $bindingsByKey = array();
75
76
    /**
77
     * Creates a new discovery.
78
     *
79
     * @param KeyValueStore        $store        The key-value store used to
80
     *                                           store the bindings and the
81
     *                                           binding types.
82
     * @param BindingInitializer[] $initializers The binding initializers to
83
     *                                           apply to newly created or
84
     *                                           unserialized bindings.
85
     */
86 86
    public function __construct(KeyValueStore $store, array $initializers = array())
87
    {
88 86
        parent::__construct($initializers);
89
90 86
        $this->store = $store;
91 86
        $this->keysByTypeName = $store->get('::keysByTypeName', array());
92 86
        $this->nextKey = $store->get('::nextKey', 0);
93 86
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 54
    public function addBindingType(BindingType $type)
99
    {
100 54
        if (isset($this->keysByTypeName[$type->getName()])) {
101 2
            throw DuplicateTypeException::forTypeName($type->getName());
102
        }
103
104 54
        $key = $this->nextKey++;
105
106 54
        $this->keysByTypeName[$type->getName()] = $key;
107
108
        // Use integer keys to reduce storage space
109
        // (compared to fully-qualified class names)
110 54
        $this->typesByKey[$key] = $type;
111
112 54
        $this->store->set('::keysByTypeName', $this->keysByTypeName);
113 54
        $this->store->set('::nextKey', $this->nextKey);
114 54
        $this->store->set('t:'.$key, $type);
115 54
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 8
    public function removeBindingType($typeName)
121
    {
122 8
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
123
124 6
        if (!isset($this->keysByTypeName[$typeName])) {
125 2
            return;
126
        }
127
128 4
        $key = $this->keysByTypeName[$typeName];
129
130
        unset(
131 4
            $this->keysByTypeName[$typeName],
132 4
            $this->typesByKey[$key],
133 4
            $this->bindingsByKey[$key]
134
        );
135
136 4
        $this->store->remove('t:'.$key);
137 4
        $this->store->remove('b:'.$key);
138 4
        $this->store->set('::keysByTypeName', $this->keysByTypeName);
139 4
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 4
    public function removeBindingTypes()
145
    {
146 4
        $this->keysByTypeName = array();
147 4
        $this->typesByKey = array();
148 4
        $this->bindingsByKey = array();
149 4
        $this->nextKey = 0;
150
151 4
        $this->store->clear();
152 4
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 10
    public function hasBindingType($typeName)
158
    {
159 10
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
160
161 8
        return isset($this->keysByTypeName[$typeName]);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167 40
    public function getBindingType($typeName)
168
    {
169 40
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
170
171 38
        if (!isset($this->keysByTypeName[$typeName])) {
172 2
            throw NoSuchTypeException::forTypeName($typeName);
173
        }
174
175 36
        $key = $this->keysByTypeName[$typeName];
176
177 36
        if (!isset($this->typesByKey[$key])) {
178 13
            $this->typesByKey[$key] = $this->store->get('t:'.$key);
179
        }
180
181 36
        return $this->typesByKey[$key];
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function hasBindingTypes()
188
    {
189
        return count($this->keysByTypeName) > 0;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 6
    public function getBindingTypes()
196
    {
197 6
        $keysToFetch = array();
198
199 6
        foreach ($this->keysByTypeName as $key) {
200 4
            if (!isset($this->typesByKey[$key])) {
201 4
                $keysToFetch[] = 't:'.$key;
202
            }
203
        }
204
205 6
        $types = $this->store->getMultiple($keysToFetch);
206
207 6
        foreach ($types as $prefixedKey => $type) {
208 2
            $this->typesByKey[substr($prefixedKey, 2)] = $type;
209
        }
210
211 6
        ksort($this->typesByKey);
212
213 6
        return $this->typesByKey;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 32
    public function addBinding(Binding $binding)
220
    {
221 32
        $typeName = $binding->getTypeName();
222
223 32
        if (!isset($this->keysByTypeName[$typeName])) {
224 2
            throw NoSuchTypeException::forTypeName($typeName);
225
        }
226
227 30
        $key = $this->keysByTypeName[$typeName];
228
229 30
        if (!isset($this->bindingsByKey[$key])) {
230 30
            $this->loadBindingsForKey($key);
231
        }
232
233 30
        $this->initializeBinding($binding);
234
235 28
        $this->bindingsByKey[$key][] = $binding;
236
237 28
        $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
238 28
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 20
    public function findBindings($typeName, Expression $expr = null)
244
    {
245 20
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
246
247 18
        if (!isset($this->keysByTypeName[$typeName])) {
248 2
            return array();
249
        }
250
251 16
        $key = $this->keysByTypeName[$typeName];
252
253 16
        if (!isset($this->bindingsByKey[$key])) {
254 10
            $this->loadBindingsForKey($key);
255
        }
256
257 16
        $bindings = $this->bindingsByKey[$key];
258
259 16
        if (null !== $expr) {
260 2
            $bindings = Expr::filter($bindings, $expr);
261
        }
262
263 16
        return $bindings;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269 30
    public function getBindings()
270
    {
271 30
        $this->loadAllBindings();
272
273 30
        $bindings = array();
274
275 30
        foreach ($this->bindingsByKey as $bindingsForKey) {
276 20
            $bindings = array_merge($bindings, $bindingsForKey);
277
        }
278
279 30
        return $bindings;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285 4
    protected function removeAllBindings()
286
    {
287 4
        $this->bindingsByKey = array();
288
289
        // Iterate $keysByTypeName which does not contain duplicate keys
290 4
        foreach ($this->keysByTypeName as $key) {
291 2
            $this->store->remove('b:'.$key);
292
        }
293 4
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298 2
    protected function removeBindingsThatMatch(Expression $expr)
299
    {
300 2
        $this->loadAllBindings();
301
302 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...
303 2
            foreach ($bindingsForKey as $i => $binding) {
304 2
                if ($expr->evaluate($binding)) {
305 2
                    unset($this->bindingsByKey[$key][$i]);
306
                }
307
            }
308
309
            // Reindex array
310 2
            $this->reindexBindingsForKey($key);
311
312 2
            $this->syncBindingsForKey($key);
313
        }
314 2
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319 6
    protected function removeBindingsWithTypeName($typeName)
320
    {
321 6
        if (!isset($this->keysByTypeName[$typeName])) {
322 2
            return;
323
        }
324
325 4
        $key = $this->keysByTypeName[$typeName];
326
327 4
        if (!isset($this->bindingsByKey[$key])) {
328
            // no initialize, since we're removing this anyway
329 2
            $this->loadBindingsForKey($key, false);
330
        }
331
332 4
        unset($this->bindingsByKey[$key]);
333
334 4
        $this->store->remove('b:'.$key);
335 4
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340 6
    protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr)
341
    {
342 6
        if (!isset($this->keysByTypeName[$typeName])) {
343 2
            return;
344
        }
345
346 4
        $key = $this->keysByTypeName[$typeName];
347
348 4
        if (!isset($this->bindingsByKey[$key])) {
349 2
            $this->loadBindingsForKey($key);
350
        }
351
352 4 View Code Duplication
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
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...
353 2
            if ($expr->evaluate($binding)) {
354 2
                unset($this->bindingsByKey[$key][$i]);
355
            }
356
        }
357
358 4
        $this->reindexBindingsForKey($key);
359 4
        $this->syncBindingsForKey($key);
360 4
    }
361
362
    /**
363
     * {@inheritdoc}
364
     */
365 4
    protected function hasAnyBinding()
366
    {
367
        // First check loaded keys
368 4
        if (count($this->bindingsByKey) > 0) {
369 1
            return true;
370
        }
371
372
        // Next check unloaded keys
373 3
        foreach ($this->keysByTypeName as $key) {
374 3
            if ($this->store->exists('b:'.$key)) {
375 3
                return true;
376
            }
377
        }
378
379 2
        return false;
380
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385
    protected function hasBindingsThatMatch(Expression $expr)
386
    {
387
        $this->loadAllBindings();
388
389
        foreach ($this->bindingsByKey as $bindingsForKey) {
390
            foreach ($bindingsForKey as $binding) {
391
                if ($expr->evaluate($binding)) {
392
                    return true;
393
                }
394
            }
395
        }
396
397
        return false;
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403 8
    protected function hasBindingsWithTypeName($typeName)
404
    {
405 8
        if (!isset($this->keysByTypeName[$typeName])) {
406 4
            return false;
407
        }
408
409 4
        $key = $this->keysByTypeName[$typeName];
410
411 4
        return isset($this->bindingsByKey[$key]) || $this->store->exists('b:'.$key);
412
    }
413
414 4
    protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr)
415
    {
416 4
        if (!$this->hasBindingsWithTypeName($typeName)) {
417 2
            return false;
418
        }
419
420 2
        $key = $this->keysByTypeName[$typeName];
421
422 2
        if (!isset($this->bindingsByKey[$key])) {
423 1
            $this->loadBindingsForKey($key);
424
        }
425
426 2
        foreach ($this->bindingsByKey[$key] as $binding) {
427 2
            if ($expr->evaluate($binding)) {
428 2
                return true;
429
            }
430
        }
431
432 2
        return false;
433
    }
434
435 30
    private function loadAllBindings()
436
    {
437 30
        $keysToFetch = array();
438
439 30
        foreach ($this->keysByTypeName as $key) {
440 20
            if (!isset($this->bindingsByKey[$key])) {
441 20
                $keysToFetch[] = 'b:'.$key;
442
            }
443
        }
444
445 30
        $fetchedBindings = $this->store->getMultiple($keysToFetch);
446
447 30
        foreach ($fetchedBindings as $key => $bindingsForKey) {
448 6
            $this->bindingsByKey[$key] = $bindingsForKey ?: array();
449 6
            $this->initializeBindings($this->bindingsByKey[$key]);
450
        }
451 30
    }
452
453 34
    private function loadBindingsForKey($key, $initialize = true)
454
    {
455 34
        $this->bindingsByKey[$key] = $this->store->get('b:'.$key, array());
456
457 34
        if ($initialize) {
458 32
            $this->initializeBindings($this->bindingsByKey[$key]);
459
        }
460 34
    }
461
462 6
    private function reindexBindingsForKey($key)
463
    {
464 6
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
465 6
    }
466
467 6
    private function syncBindingsForKey($key)
468
    {
469 6
        if (count($this->bindingsByKey[$key]) > 0) {
470 4
            $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
471
        } else {
472 2
            unset($this->bindingsByKey[$key]);
473 2
            $this->store->remove('b:'.$key);
474
        }
475 6
    }
476
}
477