Failed Conditions
Push — master ( 9d75d1...0cdc37 )
by Bernhard
07:34
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
        $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 79
        }
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 1
        }
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 3
        }
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 6
        }
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 2
        }
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 4
        }
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])) {
205 24
            $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
206 24
        }
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 3
        }
231
232 6
        foreach ($this->json['keysByTypeName'] as $key) {
233 4 View Code Duplication
            if (!isset($this->typesByKey[$key])) {
234 2
                $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
235 2
            }
236 6
        }
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 4
        }
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 55
        }
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)
283
    {
284 7
        if (null === $this->json) {
285 2
            $this->load();
286 2
        }
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 2
        }
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 5
            }
304 5
        }
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 1
        }
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 4
        }
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 2
        }
345
346 3
        foreach ($this->bindingsByKey[$key] as $binding) {
347 3
            if ($binding->getUuid()->equals($uuid)) {
348 3
                return $binding;
349
            }
350 2
        }
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 13
        }
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 13
        }
377
378 23
        $bindings = $this->bindingsByKey[$key];
379
380 23
        if (null !== $expr) {
381 2
            $bindings = $this->filterBindings($bindings, $expr);
382 2
        }
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 11
        }
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 41
        }
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 3
        }
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) {
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 2
                }
441 2
            }
442
443 2
            $this->reindexBindingsForKey($key);
444
445 2
            $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
446 2
        }
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 3
        }
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 3
        }
470
471 6
        foreach ($this->bindingsByKey[$key] as $binding) {
472 4
            unset($this->json['keysByUuid'][$binding->getUuid()->toString()]);
473 6
        }
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)
485
    {
486 8
        if (null === $this->json) {
487 3
            $this->load();
488 3
        }
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 3
        }
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 4
            }
505 6
        }
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 2
        }
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 3
        }
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 3
        }
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 1
        }
581
582 2
        foreach ($this->bindingsByKey[$key] as $binding) {
583 2
            if ($expr->evaluate($binding)) {
584 2
                return true;
585
            }
586 2
        }
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 6
                    ? unserialize($this->json['bindingsByKey'][$key])
597 6
                    : array();
598 6
                $this->initializeBindings($this->bindingsByKey[$key]);
599 6
            }
600 41
        }
601 41
    }
602
603 61
    private function loadBindingsForKey($key, $initialize = true)
604
    {
605 61
        $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
606 61
            ? unserialize($this->json['bindingsByKey'][$key])
607 61
            : array();
608
609 61
        if ($initialize) {
610 57
            $this->initializeBindings($this->bindingsByKey[$key]);
611 57
        }
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 99
            ? $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 99
        }
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