Failed Conditions
Pull Request — master (#24)
by Bernhard
22:08 queued 14:50
created

src/JsonDiscovery.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
        }
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
        }
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
        }
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
        }
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
        }
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
        }
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])) {
203 24
            $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
204
        }
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
        }
229
230 6
        foreach ($this->json['keysByTypeName'] as $key) {
231 4 View Code Duplication
            if (!isset($this->typesByKey[$key])) {
232 4
                $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
233
            }
234
        }
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
        }
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
        }
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)
281
    {
282 7
        if (null === $this->json) {
283 2
            $this->load();
284
        }
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
        }
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
            }
302
        }
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
        }
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
        }
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
        }
343
344 3
        foreach ($this->bindingsByKey[$key] as $binding) {
345 3
            if ($binding->getUuid()->equals($uuid)) {
346 3
                return $binding;
347
            }
348
        }
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
        }
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
        }
375
376 23
        $bindings = $this->bindingsByKey[$key];
377
378 23
        if (null !== $expr) {
379 2
            $bindings = $this->filterBindings($bindings, $expr);
380
        }
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
        }
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
        }
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
        }
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) {
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
                }
439
            }
440
441 2
            $this->reindexBindingsForKey($key);
442
443 2
            $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
444
        }
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
        }
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
        }
468
469 6
        foreach ($this->bindingsByKey[$key] as $binding) {
470 4
            unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
471
        }
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)
483
    {
484 8
        if (null === $this->json) {
485 3
            $this->load();
486
        }
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
        }
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
            }
503
        }
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
        }
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
        }
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
        }
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
        }
579
580 2
        foreach ($this->bindingsByKey[$key] as $binding) {
581 2
            if ($expr->evaluate($binding)) {
582 2
                return true;
583
            }
584
        }
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 4
                    ? unserialize($this->json['bindingsByKey'][$key])
595 2
                    : array();
596 31
                $this->initializeBindings($this->bindingsByKey[$key]);
597
            }
598
        }
599 41
    }
600
601 61
    private function loadBindingsForKey($key, $initialize = true)
602
    {
603 61
        $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
604 20
            ? unserialize($this->json['bindingsByKey'][$key])
605 61
            : array();
606
607 61
        if ($initialize) {
608 57
            $this->initializeBindings($this->bindingsByKey[$key]);
609
        }
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 41
            ? $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
        }
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