Completed
Push — master ( 360824...c62b77 )
by Bernhard
02:24
created

JsonDiscovery::removeBindingsWithTypeName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 10
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\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])) {
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...
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])) {
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...
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) {
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...
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) {
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...
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