Failed Conditions
Pull Request — master (#32)
by Bernhard
09:33 queued 20s
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\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\Json\JsonDecoder;
23
use Webmozart\Json\JsonEncoder;
24
25
/**
26
 * A resource discovery backed by a JSON file.
27
 *
28
 * @since  1.0
29
 *
30
 * @author Bernhard Schussek <[email protected]>
31
 */
32
class JsonDiscovery extends AbstractEditableDiscovery
33
{
34
    /**
35
     * @var string
36
     */
37
    private $path;
38
39
    /**
40
     * @var array
41
     */
42
    private $json;
43
44
    /**
45
     * @var JsonEncoder
46
     */
47
    private $encoder;
48
49
    /**
50
     * Stores the binding type for each key.
51
     *
52
     * Synchronized with the entries "t:<key>" in the store.
53
     *
54
     * @var BindingType[]
55
     */
56
    private $typesByKey = array();
57
58
    /**
59
     * Stores the bindings for each key.
60
     *
61
     * Synchronized with the entries "b:<key>" in the store.
62
     *
63
     * @var Binding[][]
64
     */
65
    private $bindingsByKey = array();
66
67
    /**
68
     * Creates a new resource discovery.
69
     *
70
     * @param string               $path         The path to the JSON file.
71
     * @param BindingInitializer[] $initializers The binding initializers to
72
     *                                           apply to newly created or
73
     *                                           unserialized bindings.
74
     */
75 99
    public function __construct($path, array $initializers = array())
76
    {
77 99
        Assert::stringNotEmpty($path, 'The path to the JSON file must be a non-empty string. Got: %s');
78
79 99
        parent::__construct($initializers);
80
81 99
        $this->path = $path;
82 99
        $this->encoder = new JsonEncoder();
83 99
        $this->encoder->setPrettyPrinting(true);
84 99
        $this->encoder->setEscapeSlash(false);
85 99
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 67
    public function addBindingType(BindingType $type)
91
    {
92 67
        if (null === $this->json) {
93 67
            $this->load();
94
        }
95
96 67
        if (isset($this->json['keysByTypeName'][$type->getName()])) {
97 2
            throw DuplicateTypeException::forTypeName($type->getName());
98
        }
99
100 67
        $key = $this->json['nextKey']++;
101
102 67
        $this->json['keysByTypeName'][$type->getName()] = $key;
103
104 67
        $this->typesByKey[$key] = $type;
105
106
        // Use integer keys to reduce storage space
107
        // (compared to fully-qualified class names)
108 67
        $this->json['typesByKey'][$key] = serialize($type);
109
110 67
        $this->flush();
111 67
    }
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 6
        unset($this->typesByKey[$key]);
136 6
        unset($this->bindingsByKey[$key]);
137
138 6
        unset($this->json['keysByTypeName'][$typeName]);
139 6
        unset($this->json['typesByKey'][$key]);
140 6
        unset($this->json['bindingsByKey'][$key]);
141
142 6
        $this->flush();
143 6
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 4
    public function removeBindingTypes()
149
    {
150 4
        if (null === $this->json) {
151
            $this->load();
152
        }
153
154 4
        $this->typesByKey = array();
155 4
        $this->bindingsByKey = array();
156
157 4
        $this->json['keysByTypeName'] = array();
158 4
        $this->json['typesByKey'] = array();
159 4
        $this->json['bindingsByKey'] = array();
160 4
        $this->json['nextKey'] = 0;
161
162 4
        $this->flush();
163 4
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 10
    public function hasBindingType($typeName)
169
    {
170 10
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
171
172 8
        if (null === $this->json) {
173 2
            $this->load();
174
        }
175
176 8
        return isset($this->json['keysByTypeName'][$typeName]);
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 53
    public function getBindingType($typeName)
183
    {
184 53
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
185
186 51
        if (null === $this->json) {
187 4
            $this->load();
188
        }
189
190 51
        if (!isset($this->json['keysByTypeName'][$typeName])) {
191 2
            throw NoSuchTypeException::forTypeName($typeName);
192
        }
193
194 49
        $key = $this->json['keysByTypeName'][$typeName];
195
196 49 View Code Duplication
        if (!isset($this->typesByKey[$key])) {
197 18
            $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
198
        }
199
200 49
        return $this->typesByKey[$key];
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function hasBindingTypes()
207
    {
208
        if (null === $this->json) {
209
            $this->load();
210
        }
211
212
        return count($this->json['keysByTypeName']) > 0;
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 6
    public function getBindingTypes()
219
    {
220 6
        if (null === $this->json) {
221 3
            $this->load();
222
        }
223
224 6
        foreach ($this->json['keysByTypeName'] as $key) {
225 4 View Code Duplication
            if (!isset($this->typesByKey[$key])) {
226 4
                $this->typesByKey[$key] = unserialize($this->json['typesByKey'][$key]);
227
            }
228
        }
229
230 6
        ksort($this->typesByKey);
231
232 6
        return $this->typesByKey;
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238 45
    public function addBinding(Binding $binding)
239
    {
240 45
        if (null === $this->json) {
241 4
            $this->load();
242
        }
243
244 45
        $typeName = $binding->getTypeName();
245
246 45
        if (!isset($this->json['keysByTypeName'][$typeName])) {
247 2
            throw NoSuchTypeException::forTypeName($typeName);
248
        }
249
250 43
        $key = $this->json['keysByTypeName'][$typeName];
251
252 43
        if (!isset($this->bindingsByKey[$key])) {
253 43
            $this->loadBindingsForKey($key);
254
        }
255
256
        // Ignore duplicates
257 43
        foreach ($this->bindingsByKey[$key] as $other) {
258 31
            if ($binding->equals($other)) {
259 31
                return;
260
            }
261
        }
262
263 43
        $this->initializeBinding($binding);
264
265 41
        $this->bindingsByKey[$key][] = $binding;
266
267 41
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
268
269 41
        $this->flush();
270 41
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275 23
    public function findBindings($typeName, Expression $expr = null)
276
    {
277 23
        Assert::stringNotEmpty($typeName, 'The type class must be a non-empty string. Got: %s');
278
279 21
        if (null === $this->json) {
280 11
            $this->load();
281
        }
282
283 21
        if (!isset($this->json['keysByTypeName'][$typeName])) {
284 2
            return array();
285
        }
286
287 19
        $key = $this->json['keysByTypeName'][$typeName];
288
289 19
        if (!isset($this->bindingsByKey[$key])) {
290 11
            $this->loadBindingsForKey($key);
291
        }
292
293 19
        $bindings = $this->bindingsByKey[$key];
294
295 19
        if (null !== $expr) {
296 2
            $bindings = Expr::filter($bindings, $expr);
297
        }
298
299 19
        return $bindings;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305 35
    public function getBindings()
306
    {
307 35
        if (null === $this->json) {
308 11
            $this->load();
309
        }
310
311 35
        $this->loadAllBindings();
312
313 35
        $bindings = array();
314
315 35
        foreach ($this->bindingsByKey as $bindingsForKey) {
316 25
            $bindings = array_merge($bindings, $bindingsForKey);
317
        }
318
319 35
        return $bindings;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325 6
    protected function removeAllBindings()
326
    {
327 6
        if (null === $this->json) {
328 3
            $this->load();
329
        }
330
331 6
        $this->bindingsByKey = array();
332
333 6
        $this->json['bindingsByKey'] = array();
334
335 6
        $this->flush();
336 6
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 2
    protected function removeBindingsThatMatch(Expression $expr)
342
    {
343 2
        if (null === $this->json) {
344
            $this->load();
345
        }
346
347 2
        $this->loadAllBindings();
348
349 2 View Code Duplication
        foreach ($this->bindingsByKey as $key => $bindingsForKey) {
350 2
            foreach ($bindingsForKey as $i => $binding) {
351 2
                if ($expr->evaluate($binding)) {
352 2
                    unset($this->bindingsByKey[$key][$i]);
353
                }
354
            }
355
356 2
            $this->reindexBindingsForKey($key);
357 2
            $this->syncBindingsForKey($key);
358
        }
359
360 2
        $this->flush();
361 2
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366 8
    protected function removeBindingsWithTypeName($typeName)
367
    {
368 8
        if (null === $this->json) {
369 3
            $this->load();
370
        }
371
372 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
373 2
            return;
374
        }
375
376 6
        $key = $this->json['keysByTypeName'][$typeName];
377
378
        unset(
379 6
            $this->bindingsByKey[$key],
380 6
            $this->json['bindingsByKey'][$key]
381
        );
382
383 6
        $this->flush();
384 6
    }
385
386
    /**
387
     * {@inheritdoc}
388
     */
389 8
    protected function removeBindingsWithTypeNameThatMatch($typeName, Expression $expr)
390
    {
391 8
        if (null === $this->json) {
392 3
            $this->load();
393
        }
394
395 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
396 2
            return;
397
        }
398
399 6
        $key = $this->json['keysByTypeName'][$typeName];
400
401 6
        if (!isset($this->bindingsByKey[$key])) {
402 3
            $this->loadBindingsForKey($key);
403
        }
404
405 6 View Code Duplication
        foreach ($this->bindingsByKey[$key] as $i => $binding) {
406 4
            if ($expr->evaluate($binding)) {
407 4
                unset($this->bindingsByKey[$key][$i]);
408
            }
409
        }
410
411 6
        $this->reindexBindingsForKey($key);
412 6
        $this->syncBindingsForKey($key);
413
414 6
        $this->flush();
415 6
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420 4
    protected function hasAnyBinding()
421
    {
422 4
        if (null === $this->json) {
423 2
            $this->load();
424
        }
425
426 4
        return count($this->json['bindingsByKey']) > 0;
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432
    protected function hasBindingsThatMatch(Expression $expr)
433
    {
434
        if (null === $this->json) {
435
            $this->load();
436
        }
437
438
        $this->loadAllBindings();
439
440
        foreach ($this->bindingsByKey as $bindingsForKey) {
441
            foreach ($bindingsForKey as $binding) {
442
                if ($expr->evaluate($binding)) {
443
                    return true;
444
                }
445
            }
446
        }
447
448
        return false;
449
    }
450
451
    /**
452
     * {@inheritdoc}
453
     */
454 8
    protected function hasBindingsWithTypeName($typeName)
455
    {
456 8
        if (null === $this->json) {
457 3
            $this->load();
458
        }
459
460 8
        if (!isset($this->json['keysByTypeName'][$typeName])) {
461 4
            return false;
462
        }
463
464 4
        $key = $this->json['keysByTypeName'][$typeName];
465
466 4
        return isset($this->json['bindingsByKey'][$key]);
467
    }
468
469 4
    protected function hasBindingsWithTypeNameThatMatch($typeName, Expression $expr)
470
    {
471 4
        if (null === $this->json) {
472 3
            $this->load();
473
        }
474
475 4
        if (!$this->hasBindingsWithTypeName($typeName)) {
476 2
            return false;
477
        }
478
479 2
        $key = $this->json['keysByTypeName'][$typeName];
480
481 2
        if (!isset($this->bindingsByKey[$key])) {
482 1
            $this->loadBindingsForKey($key);
483
        }
484
485 2
        foreach ($this->bindingsByKey[$key] as $binding) {
486 2
            if ($expr->evaluate($binding)) {
487 2
                return true;
488
            }
489
        }
490
491 2
        return false;
492
    }
493
494 35
    private function loadAllBindings()
495
    {
496 35
        foreach ($this->json['keysByTypeName'] as $key) {
497 25
            if (!isset($this->bindingsByKey[$key])) {
498 7
                $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
499 3
                    ? unserialize($this->json['bindingsByKey'][$key])
500 4
                    : array();
501 25
                $this->initializeBindings($this->bindingsByKey[$key]);
502
            }
503
        }
504 35
    }
505
506 47
    private function loadBindingsForKey($key, $initialize = true)
507
    {
508 47
        $this->bindingsByKey[$key] = isset($this->json['bindingsByKey'][$key])
509 13
            ? unserialize($this->json['bindingsByKey'][$key])
510 47
            : array();
511
512 47
        if ($initialize) {
513 45
            $this->initializeBindings($this->bindingsByKey[$key]);
514
        }
515 47
    }
516
517 8
    private function reindexBindingsForKey($key)
518
    {
519 8
        $this->bindingsByKey[$key] = array_values($this->bindingsByKey[$key]);
520
521 8
        $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
522 8
    }
523
524 8
    private function syncBindingsForKey($key)
525
    {
526 8
        if (count($this->bindingsByKey[$key]) > 0) {
527 6
            $this->json['bindingsByKey'][$key] = serialize($this->bindingsByKey[$key]);
528
        } else {
529 2
            unset($this->bindingsByKey[$key], $this->json['bindingsByKey'][$key]);
530
        }
531 8
    }
532
533
    /**
534
     * Loads the JSON file.
535
     */
536 85
    private function load()
537
    {
538 85
        $decoder = new JsonDecoder();
539 85
        $decoder->setObjectDecoding(JsonDecoder::ASSOC_ARRAY);
540
541 85
        $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...
542 34
            ? $decoder->decodeFile($this->path)
543 85
            : array();
544
545 85
        if (!isset($this->json['keysByTypeName'])) {
546 85
            $this->json['keysByTypeName'] = array();
547 85
            $this->json['typesByKey'] = array();
548 85
            $this->json['bindingsByKey'] = array();
549 85
            $this->json['nextKey'] = 0;
550
        }
551 85
    }
552
553
    /**
554
     * Writes the JSON file.
555
     */
556 69
    private function flush()
557
    {
558 69
        $this->encoder->encodeFile($this->json, $this->path);
559 69
    }
560
}
561