Completed
Push — master ( 360824...c62b77 )
by Bernhard
02:24
created

KeyValueStoreDiscovery::reindexBindingsForKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 resource 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 resource 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
        // Ignore duplicates
234 30
        foreach ($this->bindingsByKey[$key] as $other) {
235 18
            if ($binding->equals($other)) {
236 18
                return;
237
            }
238
        }
239
240 30
        $this->initializeBinding($binding);
241
242 28
        $this->bindingsByKey[$key][] = $binding;
243
244 28
        $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
245 28
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 20
    public function findBindings($typeName, Expression $expr = null)
251
    {
252 20
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
253
254 18
        if (!isset($this->keysByTypeName[$typeName])) {
255 2
            return array();
256
        }
257
258 16
        $key = $this->keysByTypeName[$typeName];
259
260 16
        if (!isset($this->bindingsByKey[$key])) {
261 10
            $this->loadBindingsForKey($key);
262
        }
263
264 16
        $bindings = $this->bindingsByKey[$key];
265
266 16
        if (null !== $expr) {
267 2
            $bindings = Expr::filter($bindings, $expr);
268
        }
269
270 16
        return $bindings;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276 30
    public function getBindings()
277
    {
278 30
        $this->loadAllBindings();
279
280 30
        $bindings = array();
281
282 30
        foreach ($this->bindingsByKey as $bindingsForKey) {
283 20
            $bindings = array_merge($bindings, $bindingsForKey);
284
        }
285
286 30
        return $bindings;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 4
    protected function removeAllBindings()
293
    {
294 4
        $this->bindingsByKey = array();
295
296
        // Iterate $keysByTypeName which does not contain duplicate keys
297 4
        foreach ($this->keysByTypeName as $key) {
298 2
            $this->store->remove('b:'.$key);
299
        }
300 4
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305 2
    protected function removeBindingsThatMatch(Expression $expr)
306
    {
307 2
        $this->loadAllBindings();
308
309 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...
310 2
            foreach ($bindingsForKey as $i => $binding) {
311 2
                if ($expr->evaluate($binding)) {
312 2
                    unset($this->bindingsByKey[$key][$i]);
313
                }
314
            }
315
316
            // Reindex array
317 2
            $this->reindexBindingsForKey($key);
318
319 2
            $this->syncBindingsForKey($key);
320
        }
321 2
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326 6
    protected function removeBindingsWithTypeName($typeName)
327
    {
328 6
        if (!isset($this->keysByTypeName[$typeName])) {
329 2
            return;
330
        }
331
332 4
        $key = $this->keysByTypeName[$typeName];
333
334 4
        if (!isset($this->bindingsByKey[$key])) {
335
            // no initialize, since we're removing this anyway
336 2
            $this->loadBindingsForKey($key, false);
337
        }
338
339 4
        unset($this->bindingsByKey[$key]);
340
341 4
        $this->store->remove('b:'.$key);
342 4
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347 6
    protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr)
348
    {
349 6
        if (!isset($this->keysByTypeName[$typeName])) {
350 2
            return;
351
        }
352
353 4
        $key = $this->keysByTypeName[$typeName];
354
355 4
        if (!isset($this->bindingsByKey[$key])) {
356 2
            $this->loadBindingsForKey($key);
357
        }
358
359 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...
360 2
            if ($expr->evaluate($binding)) {
361 2
                unset($this->bindingsByKey[$key][$i]);
362
            }
363
        }
364
365 4
        $this->reindexBindingsForKey($key);
366 4
        $this->syncBindingsForKey($key);
367 4
    }
368
369
    /**
370
     * {@inheritdoc}
371
     */
372 4
    protected function hasAnyBinding()
373
    {
374
        // First check loaded keys
375 4
        if (count($this->bindingsByKey) > 0) {
376 1
            return true;
377
        }
378
379
        // Next check unloaded keys
380 3
        foreach ($this->keysByTypeName as $key) {
381 3
            if ($this->store->exists('b:'.$key)) {
382 3
                return true;
383
            }
384
        }
385
386 2
        return false;
387
    }
388
389
    /**
390
     * {@inheritdoc}
391
     */
392
    protected function hasBindingsThatMatch(Expression $expr)
393
    {
394
        $this->loadAllBindings();
395
396
        foreach ($this->bindingsByKey as $bindingsForKey) {
397
            foreach ($bindingsForKey as $binding) {
398
                if ($expr->evaluate($binding)) {
399
                    return true;
400
                }
401
            }
402
        }
403
404
        return false;
405
    }
406
407
    /**
408
     * {@inheritdoc}
409
     */
410 8
    protected function hasBindingsWithTypeName($typeName)
411
    {
412 8
        if (!isset($this->keysByTypeName[$typeName])) {
413 4
            return false;
414
        }
415
416 4
        $key = $this->keysByTypeName[$typeName];
417
418 4
        return isset($this->bindingsByKey[$key]) || $this->store->exists('b:'.$key);
419
    }
420
421 4
    protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr)
422
    {
423 4
        if (!$this->hasBindingsWithTypeName($typeName)) {
424 2
            return false;
425
        }
426
427 2
        $key = $this->keysByTypeName[$typeName];
428
429 2
        if (!isset($this->bindingsByKey[$key])) {
430 1
            $this->loadBindingsForKey($key);
431
        }
432
433 2
        foreach ($this->bindingsByKey[$key] as $binding) {
434 2
            if ($expr->evaluate($binding)) {
435 2
                return true;
436
            }
437
        }
438
439 2
        return false;
440
    }
441
442 30
    private function loadAllBindings()
443
    {
444 30
        $keysToFetch = array();
445
446 30
        foreach ($this->keysByTypeName as $key) {
447 20
            if (!isset($this->bindingsByKey[$key])) {
448 20
                $keysToFetch[] = 'b:'.$key;
449
            }
450
        }
451
452 30
        $fetchedBindings = $this->store->getMultiple($keysToFetch);
453
454 30
        foreach ($fetchedBindings as $key => $bindingsForKey) {
455 6
            $this->bindingsByKey[$key] = $bindingsForKey ?: array();
456 6
            $this->initializeBindings($this->bindingsByKey[$key]);
457
        }
458 30
    }
459
460 34
    private function loadBindingsForKey($key, $initialize = true)
461
    {
462 34
        $this->bindingsByKey[$key] = $this->store->get('b:'.$key, array());
463
464 34
        if ($initialize) {
465 32
            $this->initializeBindings($this->bindingsByKey[$key]);
466
        }
467 34
    }
468
469 6
    private function reindexBindingsForKey($key)
470
    {
471 6
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
472 6
    }
473
474 6
    private function syncBindingsForKey($key)
475
    {
476 6
        if (count($this->bindingsByKey[$key]) > 0) {
477 4
            $this->store->set('b:'.$key, $this->bindingsByKey[$key]);
478
        } else {
479 2
            unset($this->bindingsByKey[$key]);
480 2
            $this->store->remove('b:'.$key);
481
        }
482 6
    }
483
}
484