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

JsonDiscovery::load()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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