Completed
Pull Request — master (#31)
by Bernhard
05:15
created

JsonDiscovery::hasBindingsWithTypeName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 9.4286
cc 3
eloc 7
nc 4
nop 1
crap 3
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\Json\JsonDecoder;
25
use Webmozart\Json\JsonEncoder;
26
27
/**
28
 * A resource discovery backed by a JSON file.
29
 *
30
 * @since  1.0
31
 *
32
 * @author Bernhard Schussek <[email protected]>
33
 */
34
class JsonDiscovery extends AbstractEditableDiscovery
35
{
36
    /**
37
     * @var string
38
     */
39
    private $path;
40
41
    /**
42
     * @var array
43
     */
44
    private $json;
45
46
    /**
47
     * @var JsonEncoder
48
     */
49
    private $encoder;
50
51
    /**
52
     * Stores the binding type for each key.
53
     *
54
     * Synchronized with the entries "t:<key>" in the store.
55
     *
56
     * @var BindingType[]
57
     */
58
    private $typesByKey = array();
59
60
    /**
61
     * Stores the bindings for each key.
62
     *
63
     * Synchronized with the entries "b:<key>" in the store.
64
     *
65
     * @var Binding[][]
66
     */
67
    private $bindingsByKey = array();
68
69
    /**
70
     * Creates a new resource discovery.
71
     *
72
     * @param string               $path         The path to the JSON file.
73
     * @param BindingInitializer[] $initializers The binding initializers to
74
     *                                           apply to newly created or
75
     *                                           unserialized bindings.
76
     */
77 113
    public function __construct($path, array $initializers = array())
78
    {
79 113
        Assert::stringNotEmpty($path, 'The path to the JSON file must be a non-empty string. Got: %s');
80
81 113
        parent::__construct($initializers);
82
83 113
        $this->path = $path;
84 113
        $this->encoder = new JsonEncoder();
85 113
        $this->encoder->setPrettyPrinting(true);
86 113
        $this->encoder->setEscapeSlash(false);
87 113
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 79
    public function addBindingType(BindingType $type)
93
    {
94 79
        if (null === $this->json) {
95 79
            $this->load();
96
        }
97
98 79
        if (isset($this->json['keysByTypeName'][$type->getName()])) {
99 2
            throw DuplicateTypeException::forTypeName($type->getName());
100
        }
101
102 79
        $key = $this->json['nextKey']++;
103
104 79
        $this->json['keysByTypeName'][$type->getName()] = $key;
105
106 79
        $this->typesByKey[$key] = $type;
107
108
        // Use integer keys to reduce storage space
109
        // (compared to fully-qualified class names)
110 79
        $this->json['typesByKey'][$key] = serialize($type);
111
112 79
        $this->flush();
113 79
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 10
    public function removeBindingType($typeName)
119
    {
120 10
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
121
122 8
        if (null === $this->json) {
123 1
            $this->load();
124
        }
125
126 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
127 2
            return;
128
        }
129
130 6
        $key = $this->json['keysByTypeName'][$typeName];
131
132 6
        if (!isset($this->bindingsByKey[$key])) {
133
            // no initialize, since we're removing this anyway
134 3
            $this->loadBindingsForKey($key, false);
135
        }
136
137
        // Remove all binding UUIDs for this binding type
138 6
        foreach ($this->bindingsByKey[$key] as $binding) {
139 4
            unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
140
        }
141
142 6
        unset($this->typesByKey[$key]);
143 6
        unset($this->bindingsByKey[$key]);
144
145 6
        unset($this->json['keysByTypeName'][$typeName]);
146 6
        unset($this->json['typesByKey'][$key]);
147 6
        unset($this->json['bindingsByKey'][$key]);
148
149 6
        $this->flush();
150 6
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 4
    public function removeBindingTypes()
156
    {
157 4
        if (null === $this->json) {
158
            $this->load();
159
        }
160
161 4
        $this->typesByKey = array();
162 4
        $this->bindingsByKey = array();
163
164 4
        $this->json['keysByTypeName'] = array();
165 4
        $this->json['keysByUuid'] = array();
166 4
        $this->json['typesByKey'] = array();
167 4
        $this->json['bindingsByKey'] = array();
168 4
        $this->json['nextKey'] = 0;
169
170 4
        $this->flush();
171 4
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 10
    public function hasBindingType($typeName)
177
    {
178 10
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
179
180 8
        if (null === $this->json) {
181 2
            $this->load();
182
        }
183
184 8
        return isset($this->json['keysByTypeName'][$typeName]);
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 65
    public function getBindingType($typeName)
191
    {
192 65
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
193
194 63
        if (null === $this->json) {
195 4
            $this->load();
196
        }
197
198 63
        if (!isset($this->json['keysByTypeName'][$typeName])) {
199 2
            throw NoSuchTypeException::forTypeName($typeName);
200
        }
201
202 61
        $key = $this->json['keysByTypeName'][$typeName];
203
204 61 View Code Duplication
        if (!isset($this->typesByKey[$key])) {
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...
205 24
            $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
206
        }
207
208 61
        return $this->typesByKey[$key];
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function hasBindingTypes()
215
    {
216
        if (null === $this->json) {
217
            $this->load();
218
        }
219
220
        return count($this->json['keysByTypeName']) > 0;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226 6
    public function getBindingTypes()
227
    {
228 6
        if (null === $this->json) {
229 3
            $this->load();
230
        }
231
232 6
        foreach ($this->json['keysByTypeName'] as $key) {
233 4 View Code Duplication
            if (!isset($this->typesByKey[$key])) {
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...
234 4
                $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
235
            }
236
        }
237
238 6
        ksort($this->typesByKey);
239
240 6
        return $this->typesByKey;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 57
    public function addBinding(Binding $binding)
247
    {
248 57
        if (null === $this->json) {
249 4
            $this->load();
250
        }
251
252 57
        $typeName = $binding->getTypeName();
253
254 57
        if (!isset($this->json['keysByTypeName'][$typeName])) {
255 2
            throw NoSuchTypeException::forTypeName($typeName);
256
        }
257
258 55
        if (isset($this->json['keysByUuid'][$binding->getUuid()->toString()])) {
259
            // Ignore duplicates
260 2
            return;
261
        }
262
263 55
        $key = $this->json['keysByTypeName'][$typeName];
264
265 55
        if (!isset($this->bindingsByKey[$key])) {
266 55
            $this->loadBindingsForKey($key);
267
        }
268
269 55
        $this->initializeBinding($binding);
270
271 53
        $this->bindingsByKey[$key][] = $binding;
272
273 53
        $this->json['keysByUuid'][$binding->getUuid()->toString()] = $key;
274 53
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
275
276 53
        $this->flush();
277 53
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282 7 View Code Duplication
    public function removeBinding(Uuid $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
283
    {
284 7
        if (null === $this->json) {
285 2
            $this->load();
286
        }
287
288 7
        $uuidString = $uuid->toString();
289
290 7
        if (!isset($this->json['keysByUuid'][$uuidString])) {
291 2
            return;
292
        }
293
294 5
        $key = $this->json['keysByUuid'][$uuidString];
295
296 5
        if (!isset($this->bindingsByKey[$key])) {
297 2
            $this->loadBindingsForKey($key);
298
        }
299
300 5
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
301 5
            if ($binding->getUuid()->equals($uuid)) {
302 5
                unset($this->bindingsByKey[$key][$i]);
303
            }
304
        }
305
306 5
        $this->reindexBindingsForKey($key);
307
308 5
        unset($this->json['keysByUuid'][$uuidString]);
309
310 5
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
311
312 5
        $this->flush();
313 5
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318 20
    public function hasBinding(Uuid $uuid)
319
    {
320 20
        if (null === $this->json) {
321 1
            $this->load();
322
        }
323
324 20
        return isset($this->json['keysByUuid'][$uuid->toString()]);
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330 5
    public function getBinding(Uuid $uuid)
331
    {
332 5
        if (null === $this->json) {
333 4
            $this->load();
334
        }
335
336 5
        if (!isset($this->json['keysByUuid'][$uuid->toString()])) {
337 2
            throw NoSuchBindingException::forUuid($uuid);
338
        }
339
340 3
        $key = $this->json['keysByUuid'][$uuid->toString()];
341
342 3
        if (!isset($this->bindingsByKey[$key])) {
343 2
            $this->loadBindingsForKey($key);
344
        }
345
346 3
        foreach ($this->bindingsByKey[$key] as $binding) {
347 3
            if ($binding->getUuid()->equals($uuid)) {
348 3
                return $binding;
349
            }
350
        }
351
352
        // This does not happen except if someone plays with the JSON file
353
        // contents (or there's a bug here..)
354
        throw new RuntimeException('The discovery is corrupt. Please rebuild it.');
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360 27
    public function findBindings($typeName, Expression $expr = null)
361
    {
362 27
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
363
364 25
        if (null === $this->json) {
365 13
            $this->load();
366
        }
367
368 25
        if (!isset($this->json['keysByTypeName'][$typeName])) {
369 2
            return array();
370
        }
371
372 23
        $key = $this->json['keysByTypeName'][$typeName];
373
374 23
        if (!isset($this->bindingsByKey[$key])) {
375 13
            $this->loadBindingsForKey($key);
376
        }
377
378 23
        $bindings = $this->bindingsByKey[$key];
379
380 23
        if (null !== $expr) {
381 2
            $bindings = $this->filterBindings($bindings, $expr);
382
        }
383
384 23
        return $bindings;
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390 41
    public function getBindings()
391
    {
392 41
        if (null === $this->json) {
393 11
            $this->load();
394
        }
395
396 41
        $this->loadAllBindings();
397
398 41
        $bindings = array();
399
400 41
        foreach ($this->bindingsByKey as $bindingsForKey) {
401 31
            $bindings = array_merge($bindings, $bindingsForKey);
402
        }
403
404 41
        return $bindings;
405
    }
406
407
    /**
408
     * {@inheritdoc}
409
     */
410 6
    protected function removeAllBindings()
411
    {
412 6
        if (null === $this->json) {
413 3
            $this->load();
414
        }
415
416 6
        $this->bindingsByKey = array();
417
418 6
        $this->json['bindingsByKey'] = array();
419 6
        $this->json['keysByUuid'] = array();
420
421 6
        $this->flush();
422 6
    }
423
424
    /**
425
     * {@inheritdoc}
426
     */
427 2
    protected function removeBindingsThatMatch(Expression $expr)
428
    {
429 2
        if (null === $this->json) {
430
            $this->load();
431
        }
432
433 2
        $this->loadAllBindings();
434
435 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...
436 2
            foreach ($bindingsForKey as $i => $binding) {
437 2
                if ($expr->evaluate($binding)) {
438 2
                    unset($this->bindingsByKey[$key][$i]);
439 2
                    unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
440
                }
441
            }
442
443 2
            $this->reindexBindingsForKey($key);
444
445 2
            $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
446
        }
447
448 2
        $this->flush();
449 2
    }
450
451
    /**
452
     * {@inheritdoc}
453
     */
454 8
    protected function removeBindingsWithTypeName($typeName)
455
    {
456 8
        if (null === $this->json) {
457 3
            $this->load();
458
        }
459
460 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
461 2
            return;
462
        }
463
464 6
        $key = $this->json['keysByTypeName'][$typeName];
465
466 6
        if (!isset($this->bindingsByKey[$key])) {
467
            // no initialize, since we're removing this anyway
468 3
            $this->loadBindingsForKey($key, false);
469
        }
470
471 6
        foreach ($this->bindingsByKey[$key] as $binding) {
472 4
            unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
473
        }
474
475 6
        unset($this->bindingsByKey[$key]);
476 6
        unset($this->json['bindingsByKey'][$key]);
477
478 6
        $this->flush();
479 6
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484 8 View Code Duplication
    protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
485
    {
486 8
        if (null === $this->json) {
487 3
            $this->load();
488
        }
489
490 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
491 2
            return;
492
        }
493
494 6
        $key = $this->json['keysByTypeName'][$typeName];
495
496 6
        if (!isset($this->bindingsByKey[$key])) {
497 3
            $this->loadBindingsForKey($key);
498
        }
499
500 6
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
501 4
            if ($expr->evaluate($binding)) {
502 4
                unset($this->bindingsByKey[$key][$i]);
503 4
                unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
504
            }
505
        }
506
507 6
        $this->reindexBindingsForKey($key);
508
509 6
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
510
511 6
        $this->flush();
512 6
    }
513
514
    /**
515
     * {@inheritdoc}
516
     */
517 4
    protected function hasAnyBinding()
518
    {
519 4
        if (null === $this->json) {
520 2
            $this->load();
521
        }
522
523 4
        return count($this->json['keysByUuid']) > 0;
524
    }
525
526
    /**
527
     * {@inheritdoc}
528
     */
529
    protected function hasBindingsThatMatch(Expression $expr)
530
    {
531
        if (null === $this->json) {
532
            $this->load();
533
        }
534
535
        $this->loadAllBindings();
536
537
        foreach ($this->bindingsByKey as $bindingsForKey) {
538
            foreach ($bindingsForKey as $binding) {
539
                if ($expr->evaluate($binding)) {
540
                    return true;
541
                }
542
            }
543
        }
544
545
        return false;
546
    }
547
548
    /**
549
     * {@inheritdoc}
550
     */
551 8
    protected function hasBindingsWithTypeName($typeName)
552
    {
553 8
        if (null === $this->json) {
554 3
            $this->load();
555
        }
556
557 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
558 4
            return false;
559
        }
560
561 4
        $key = $this->json['keysByTypeName'][$typeName];
562
563 4
        return false !== array_search($key, $this->json['keysByUuid'], true);
564
    }
565
566 4
    protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr)
567
    {
568 4
        if (null === $this->json) {
569 3
            $this->load();
570
        }
571
572 4
        if (!$this->hasBindingsWithTypeName($typeName)) {
573 2
            return false;
574
        }
575
576 2
        $key = $this->json['keysByTypeName'][$typeName];
577
578 2
        if (!isset($this->bindingsByKey[$key])) {
579 1
            $this->loadBindingsForKey($key);
580
        }
581
582 2
        foreach ($this->bindingsByKey[$key] as $binding) {
583 2
            if ($expr->evaluate($binding)) {
584 2
                return true;
585
            }
586
        }
587
588 2
        return false;
589
    }
590
591 41
    private function loadAllBindings()
592
    {
593 41
        foreach ($this->json['keysByTypeName'] as $key) {
594 31
            if (!isset($this->bindingsByKey[$key])) {
595 6
                $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
596 4
                    ? unserialize($this->json['bindingsByKey'][$key])
597 2
                    : array();
598 31
                $this->initializeBindings($this->bindingsByKey[$key]);
599
            }
600
        }
601 41
    }
602
603 61
    private function loadBindingsForKey($key, $initialize = true)
604
    {
605 61
        $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
606 20
            ? unserialize($this->json['bindingsByKey'][$key])
607 61
            : array();
608
609 61
        if ($initialize) {
610 57
            $this->initializeBindings($this->bindingsByKey[$key]);
611
        }
612 61
    }
613
614 13
    private function reindexBindingsForKey($key)
615
    {
616 13
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
617
618 13
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
619 13
    }
620
621
    /**
622
     * Loads the JSON file.
623
     */
624 99
    private function load()
625
    {
626 99
        $decoder = new JsonDecoder();
627 99
        $decoder->setObjectDecoding(JsonDecoder::ASSOC_ARRAY);
628
629 99
        $this->json = file_exists($this->path)
0 ignored issues
show
Documentation Bug introduced by
It seems like file_exists($this->path)...($this->path) : array() of type * is incompatible with the declared type array of property $json.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
630 41
            ? $decoder->decodeFile($this->path)
631 99
            : array();
632
633 99
        if (!isset($this->json['keysByTypeName'])) {
634 99
            $this->json['keysByTypeName'] = array();
635 99
            $this->json['keysByUuid'] = array();
636 99
            $this->json['typesByKey'] = array();
637 99
            $this->json['bindingsByKey'] = array();
638 99
            $this->json['nextKey'] = 0;
639
        }
640 99
    }
641
642
    /**
643
     * Writes the JSON file.
644
     */
645 81
    private function flush()
646
    {
647 81
        $this->encoder->encodeFile($this->json, $this->path);
648 81
    }
649
}
650