DiscoveryManagerImpl   F
last analyzed

Complexity

Total Complexity 154

Size/Duplication

Total Lines 1019
Duplicated Lines 28.26 %

Coupling/Cohesion

Components 1
Dependencies 45

Test Coverage

Coverage 98.19%

Importance

Changes 11
Bugs 3 Features 2
Metric Value
wmc 154
c 11
b 3
f 2
lcom 1
cbo 45
dl 288
loc 1019
ccs 435
cts 443
cp 0.9819
rs 0.9443

56 Methods

Rating   Name   Duplication   Size   Complexity  
A getContext() 0 4 1
A clearRootTypeDescriptors() 0 4 1
A getRootTypeDescriptor() 0 4 1
A hasRootTypeDescriptor() 0 4 1
A clearRootBindingDescriptors() 0 4 1
A hasRootBindingDescriptor() 0 4 2
A loadBindingDescriptor() 0 4 1
A clearDiscovery() 0 4 1
A addBindingType() 0 4 1
A unloadBindingDescriptor() 0 4 1
A addBinding() 0 4 1
A findRootTypeDescriptors() 7 7 1
A hasRootTypeDescriptors() 10 10 2
A findRootBindingDescriptors() 7 7 1
A hasRootBindingDescriptors() 10 10 2
A findBindingDescriptors() 14 14 3
A assertBindingValid() 0 10 4
A getUuidsByTypeName() 0 12 3
A unloadTypeDescriptor() 12 12 1
B addRootTypeDescriptor() 5 44 6
B removeRootTypeDescriptor() 5 47 5
B removeRootTypeDescriptors() 5 43 6
A getRootTypeDescriptors() 15 15 3
A getTypeDescriptor() 0 13 2
A getTypeDescriptors() 0 14 3
A findTypeDescriptors() 16 16 4
A hasTypeDescriptor() 0 8 1
B hasTypeDescriptors() 0 18 5
C addRootBindingDescriptor() 0 48 11
B removeRootBindingDescriptor() 0 33 4
B removeRootBindingDescriptors() 27 27 4
A getRootBindingDescriptor() 0 10 2
A getRootBindingDescriptors() 14 14 3
C enableBindingDescriptor() 45 45 7
C disableBindingDescriptor() 45 45 7
C removeObsoleteDisabledBindingDescriptors() 0 29 7
A hasBindingDescriptors() 16 16 4
D buildDiscovery() 0 34 9
A assertModulesLoaded() 0 6 2
C loadModules() 9 32 8
A emitWarningForDuplicateTypes() 0 18 3
A emitWarningForInvalidBindings() 0 13 3
A saveRootModuleFile() 0 4 1
A addTypeDescriptorToModuleFile() 0 4 1
A removeTypeDescriptorFromModuleFile() 0 4 1
A loadTypeDescriptor() 12 12 1
A addBindingDescriptorToModuleFile() 0 4 1
A removeBindingDescriptorFromModuleFile() 0 4 1
A __construct() 0 15 2
A getBindingDescriptor() 0 10 2
A getBindingDescriptors() 0 6 1
A hasBindingDescriptor() 0 6 1
A syncTypeName() 0 4 1
A enableBindingUuid() 7 7 1
A disableBindingUuid() 7 7 1
A syncBindingUuid() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DiscoveryManagerImpl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DiscoveryManagerImpl, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/manager 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\Manager\Discovery;
13
14
use Exception;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
use Puli\Discovery\Api\EditableDiscovery;
18
use Puli\Manager\Api\Context\ProjectContext;
19
use Puli\Manager\Api\Discovery\BindingDescriptor;
20
use Puli\Manager\Api\Discovery\BindingTypeDescriptor;
21
use Puli\Manager\Api\Discovery\DiscoveryManager;
22
use Puli\Manager\Api\Discovery\DiscoveryNotEmptyException;
23
use Puli\Manager\Api\Discovery\DuplicateBindingException;
24
use Puli\Manager\Api\Discovery\DuplicateTypeException;
25
use Puli\Manager\Api\Discovery\NoSuchBindingException;
26
use Puli\Manager\Api\Discovery\NoSuchTypeException;
27
use Puli\Manager\Api\Discovery\TypeNotEnabledException;
28
use Puli\Manager\Api\Module\InstallInfo;
29
use Puli\Manager\Api\Module\Module;
30
use Puli\Manager\Api\Module\ModuleList;
31
use Puli\Manager\Api\Module\RootModule;
32
use Puli\Manager\Api\Module\RootModuleFile;
33
use Puli\Manager\Api\NonRootModuleExpectedException;
34
use Puli\Manager\Assert\Assert;
35
use Puli\Manager\Discovery\Binding\AddBinding;
36
use Puli\Manager\Discovery\Binding\AddBindingDescriptorToModuleFile;
37
use Puli\Manager\Discovery\Binding\BindingDescriptorCollection;
38
use Puli\Manager\Discovery\Binding\DisableBindingUuid;
39
use Puli\Manager\Discovery\Binding\EnableBindingUuid;
40
use Puli\Manager\Discovery\Binding\LoadBindingDescriptor;
41
use Puli\Manager\Discovery\Binding\ReloadBindingDescriptorsByTypeName;
42
use Puli\Manager\Discovery\Binding\ReloadBindingDescriptorsByUuid;
43
use Puli\Manager\Discovery\Binding\RemoveBindingDescriptorFromModuleFile;
44
use Puli\Manager\Discovery\Binding\SyncBindingUuid;
45
use Puli\Manager\Discovery\Binding\UnloadBindingDescriptor;
46
use Puli\Manager\Discovery\Type\AddBindingType;
47
use Puli\Manager\Discovery\Type\AddTypeDescriptorToModuleFile;
48
use Puli\Manager\Discovery\Type\BindingTypeDescriptorCollection;
49
use Puli\Manager\Discovery\Type\LoadTypeDescriptor;
50
use Puli\Manager\Discovery\Type\RemoveTypeDescriptorFromModuleFile;
51
use Puli\Manager\Discovery\Type\SyncTypeName;
52
use Puli\Manager\Discovery\Type\UnloadTypeDescriptor;
53
use Puli\Manager\Discovery\Type\UpdateDuplicateMarksForTypeName;
54
use Puli\Manager\Json\JsonStorage;
55
use Puli\Manager\Transaction\InterceptedOperation;
56
use Puli\Manager\Transaction\Transaction;
57
use Rhumsaa\Uuid\Uuid;
58
use Webmozart\Expression\Expr;
59
use Webmozart\Expression\Expression;
60
61
/**
62
 * @since  1.0
63
 *
64
 * @author Bernhard Schussek <[email protected]>
65
 */
66
class DiscoveryManagerImpl implements DiscoveryManager
67
{
68
    /**
69
     * @var ProjectContext
70
     */
71
    private $context;
72
73
    /**
74
     * @var LoggerInterface
75
     */
76
    private $logger;
77
78
    /**
79
     * @var EditableDiscovery
80
     */
81
    private $discovery;
82
83
    /**
84
     * @var ModuleList
85
     */
86
    private $modules;
87
88
    /**
89
     * @var JsonStorage
90
     */
91
    private $jsonStorage;
92
93
    /**
94
     * @var RootModule
95
     */
96
    private $rootModule;
97
98
    /**
99
     * @var RootModuleFile
100
     */
101
    private $rootModuleFile;
102
103
    /**
104
     * @var BindingTypeDescriptorCollection
105
     */
106
    private $typeDescriptors;
107
108
    /**
109
     * @var BindingDescriptorCollection
110
     */
111
    private $bindingDescriptors;
112
113
    /**
114
     * Creates a tag manager.
115
     *
116
     * @param ProjectContext       $context
117
     * @param EditableDiscovery    $discovery
118
     * @param ModuleList           $modules
119
     * @param JsonStorage          $jsonStorage
120
     * @param LoggerInterface|null $logger
121
     */
122 99
    public function __construct(
123
        ProjectContext $context,
124
        EditableDiscovery $discovery,
125
        ModuleList $modules,
126
        JsonStorage $jsonStorage,
127
        LoggerInterface $logger = null
128
    ) {
129 99
        $this->context = $context;
130 99
        $this->discovery = $discovery;
131 99
        $this->modules = $modules;
132 99
        $this->jsonStorage = $jsonStorage;
133 99
        $this->rootModule = $modules->getRootModule();
134 99
        $this->rootModuleFile = $context->getRootModuleFile();
135 99
        $this->logger = $logger ?: new NullLogger();
136 99
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 1
    public function getContext()
142
    {
143 1
        return $this->context;
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149 5
    public function addRootTypeDescriptor(BindingTypeDescriptor $typeDescriptor, $flags = 0)
150
    {
151 5
        Assert::integer($flags, 'The argument $flags must be a boolean.');
152
153 5
        $this->assertModulesLoaded();
154 5
        $this->emitWarningForDuplicateTypes();
155
156 5
        $typeName = $typeDescriptor->getTypeName();
157
158 5
        if (!($flags & self::OVERRIDE) && $this->typeDescriptors->contains($typeName)) {
159 1
            throw DuplicateTypeException::forTypeName($typeName);
160
        }
161
162 4
        $tx = new Transaction();
163
164
        try {
165 4
            $syncBindingOps = array();
166
167 4 View Code Duplication
            foreach ($this->getUuidsByTypeName($typeName) as $uuid) {
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...
168 1
                $syncBindingOp = $this->syncBindingUuid($uuid);
169 1
                $syncBindingOp->takeSnapshot();
170 1
                $syncBindingOps[] = $syncBindingOp;
171
            }
172
173 4
            $syncOp = $this->syncTypeName($typeName);
174 4
            $syncOp->takeSnapshot();
175
176 4
            $tx->execute($this->loadTypeDescriptor($typeDescriptor, $this->rootModule));
177 4
            $tx->execute($this->addTypeDescriptorToModuleFile($typeDescriptor));
178 4
            $tx->execute($syncOp);
179
180 4
            foreach ($syncBindingOps as $syncBindingOp) {
181 1
                $tx->execute($syncBindingOp);
182
            }
183
184 4
            $this->saveRootModuleFile();
185
186 3
            $tx->commit();
187 1
        } catch (Exception $e) {
188 1
            $tx->rollback();
189
190 1
            throw $e;
191
        }
192 3
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 11
    public function removeRootTypeDescriptor($typeName)
198
    {
199
        // Only check that this is a string. The error message "not found" is
200
        // more helpful than e.g. "type name must contain /".
201 11
        Assert::string($typeName, 'The type name must be a string');
202
203 11
        $this->assertModulesLoaded();
204
205 11
        if (!$this->typeDescriptors->contains($typeName, $this->rootModule->getName())) {
206 2
            return;
207
        }
208
209 9
        $typeDescriptor = $this->typeDescriptors->get($typeName, $this->rootModule->getName());
210 9
        $tx = new Transaction();
211
212
        try {
213 9
            $tx->execute($this->removeTypeDescriptorFromModuleFile($typeName));
214
215 9
            $syncBindingOps = array();
216
217 9 View Code Duplication
            foreach ($this->getUuidsByTypeName($typeName) as $uuid) {
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...
218 3
                $syncBindingOp = $this->syncBindingUuid($uuid);
219 3
                $syncBindingOp->takeSnapshot();
220 3
                $syncBindingOps[] = $syncBindingOp;
221
            }
222
223 9
            $syncOp = $this->syncTypeName($typeName);
224 9
            $syncOp->takeSnapshot();
225
226 9
            $tx->execute($this->unloadTypeDescriptor($typeDescriptor));
227 9
            $tx->execute($syncOp);
228
229 9
            foreach ($syncBindingOps as $syncBindingOp) {
230 3
                $tx->execute($syncBindingOp);
231
            }
232
233 9
            $this->saveRootModuleFile();
234
235 8
            $tx->commit();
236 1
        } catch (Exception $e) {
237 1
            $tx->rollback();
238
239 1
            throw $e;
240
        }
241
242 8
        $this->emitWarningForDuplicateTypes();
243 8
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 4
    public function removeRootTypeDescriptors(Expression $expr)
249
    {
250 4
        $this->assertModulesLoaded();
251
252 4
        $tx = new Transaction();
253 4
        $syncBindingOps = array();
254
255
        try {
256 4
            foreach ($this->getRootTypeDescriptors() as $typeDescriptor) {
257 4
                if ($expr->evaluate($typeDescriptor)) {
258 4
                    $typeName = $typeDescriptor->getTypeName();
259
260 4
                    $tx->execute($this->removeTypeDescriptorFromModuleFile($typeName));
261
262 4 View Code Duplication
                    foreach ($this->getUuidsByTypeName($typeName) as $uuid) {
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...
263 1
                        $syncBindingOp = $this->syncBindingUuid($uuid);
264 1
                        $syncBindingOp->takeSnapshot();
265 1
                        $syncBindingOps[] = $syncBindingOp;
266
                    }
267
268 4
                    $syncOp = $this->syncTypeName($typeName);
269 4
                    $syncOp->takeSnapshot();
270
271 4
                    $tx->execute($this->unloadTypeDescriptor($typeDescriptor));
272 4
                    $tx->execute($syncOp);
273
                }
274
            }
275
276 4
            foreach ($syncBindingOps as $syncBindingOp) {
277 1
                $tx->execute($syncBindingOp);
278
            }
279
280 4
            $this->saveRootModuleFile();
281
282 3
            $tx->commit();
283 1
        } catch (Exception $e) {
284 1
            $tx->rollback();
285
286 1
            throw $e;
287
        }
288
289 3
        $this->emitWarningForDuplicateTypes();
290 3
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 1
    public function clearRootTypeDescriptors()
296
    {
297 1
        $this->removeRootTypeDescriptors(Expr::true());
298 1
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303 2
    public function getRootTypeDescriptor($typeName)
304
    {
305 2
        return $this->getTypeDescriptor($typeName, $this->rootModule->getName());
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311 5 View Code Duplication
    public function getRootTypeDescriptors()
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...
312
    {
313 5
        $this->assertModulesLoaded();
314
315 5
        $types = array();
316 5
        $rootModuleName = $this->rootModule->getName();
317
318 5
        foreach ($this->typeDescriptors->toArray() as $typeName => $typesByModule) {
319 5
            if (isset($typesByModule[$rootModuleName])) {
320 5
                $types[] = $typesByModule[$rootModuleName];
321
            }
322
        }
323
324 5
        return $types;
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330 1 View Code Duplication
    public function findRootTypeDescriptors(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...
331
    {
332 1
        $expr = Expr::method('getContainingModule', Expr::same($this->rootModule))
333 1
            ->andX($expr);
334
335 1
        return $this->findTypeDescriptors($expr);
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 1
    public function hasRootTypeDescriptor($typeName)
342
    {
343 1
        return $this->hasTypeDescriptor($typeName, $this->rootModule->getName());
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     */
349 1 View Code Duplication
    public function hasRootTypeDescriptors(Expression $expr = null)
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...
350
    {
351 1
        $expr2 = Expr::method('getContainingModule', Expr::same($this->rootModule));
352
353 1
        if ($expr) {
354 1
            $expr2 = $expr2->andX($expr);
355
        }
356
357 1
        return $this->hasTypeDescriptors($expr2);
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363 6
    public function getTypeDescriptor($typeName, $moduleName)
364
    {
365 6
        Assert::string($typeName, 'The type name must be a string. Got: %s');
366 5
        Assert::string($moduleName, 'The module name must be a string. Got: %s');
367
368 4
        $this->assertModulesLoaded();
369
370 4
        if (!$this->typeDescriptors->contains($typeName, $moduleName)) {
371 2
            throw NoSuchTypeException::forTypeName($typeName);
372
        }
373
374 2
        return $this->typeDescriptors->get($typeName, $moduleName);
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     */
380 2
    public function getTypeDescriptors()
381
    {
382 2
        $this->assertModulesLoaded();
383
384 2
        $types = array();
385
386 2
        foreach ($this->typeDescriptors->toArray() as $typeName => $typesByModule) {
387 1
            foreach ($typesByModule as $type) {
388 1
                $types[] = $type;
389
            }
390
        }
391
392 2
        return $types;
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398 2 View Code Duplication
    public function findTypeDescriptors(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...
399
    {
400 2
        $this->assertModulesLoaded();
401
402 2
        $typeDescriptors = array();
403
404 2
        foreach ($this->typeDescriptors->toArray() as $typeName => $descriptorsByModule) {
405 2
            foreach ($descriptorsByModule as $typeDescriptor) {
406 2
                if ($expr->evaluate($typeDescriptor)) {
407 2
                    $typeDescriptors[] = $typeDescriptor;
408
                }
409
            }
410
        }
411
412 2
        return $typeDescriptors;
413
    }
414
415
    /**
416
     * {@inheritdoc}
417
     */
418 3
    public function hasTypeDescriptor($typeName, $moduleName = null)
419
    {
420 3
        Assert::nullOrString($moduleName, 'The module name must be a string or null. Got: %s');
421
422 2
        $this->assertModulesLoaded();
423
424 2
        return $this->typeDescriptors->contains($typeName, $moduleName);
425
    }
426
427
    /**
428
     * {@inheritdoc}
429
     */
430 3
    public function hasTypeDescriptors(Expression $expr = null)
431
    {
432 3
        $this->assertModulesLoaded();
433
434 3
        if (!$expr) {
435 2
            return !$this->typeDescriptors->isEmpty();
436
        }
437
438 2
        foreach ($this->typeDescriptors->toArray() as $typeName => $descriptorsByModule) {
439 2
            foreach ($descriptorsByModule as $typeDescriptor) {
440 2
                if ($expr->evaluate($typeDescriptor)) {
441 2
                    return true;
442
                }
443
            }
444
        }
445
446 2
        return false;
447
    }
448
449
    /**
450
     * {@inheritdoc}
451
     */
452 13
    public function addRootBindingDescriptor(BindingDescriptor $bindingDescriptor, $flags = 0)
453
    {
454 13
        $this->assertModulesLoaded();
455
456 13
        $typeName = $bindingDescriptor->getTypeName();
457 13
        $typeExists = $this->typeDescriptors->contains($typeName);
458
459 13
        if (!($flags & self::IGNORE_TYPE_NOT_FOUND) && !$typeExists) {
460 2
            throw NoSuchTypeException::forTypeName($typeName);
461
        }
462
463 11
        if (!($flags & self::IGNORE_TYPE_NOT_ENABLED) && $typeExists && !$this->typeDescriptors->getFirst($typeName)->isEnabled()) {
464 2
            throw TypeNotEnabledException::forTypeName($typeName);
465
        }
466
467 9
        $uuid = $bindingDescriptor->getUuid();
468 9
        $exists = $this->bindingDescriptors->contains($uuid);
469 9
        $existsInNonRoot = $exists
470 4
            ? !($this->bindingDescriptors->get($uuid)->getContainingModule() instanceof RootModule)
471 9
            : false;
472
473
        // We can only override bindings in the root module
474 9
        if ($existsInNonRoot || ($exists && !($flags & self::OVERRIDE))) {
475 3
            throw DuplicateBindingException::forUuid($uuid);
476
        }
477
478 6
        $tx = new Transaction();
479
480
        try {
481 6
            $syncOp = $this->syncBindingUuid($uuid);
482 6
            $syncOp->takeSnapshot();
483
484 6
            $tx->execute($this->loadBindingDescriptor($bindingDescriptor, $this->rootModule));
485
486 6
            $this->assertBindingValid($bindingDescriptor);
487
488 5
            $tx->execute($this->addBindingDescriptorToModuleFile($bindingDescriptor));
489 5
            $tx->execute($syncOp);
490
491 5
            $this->saveRootModuleFile();
492
493 4
            $tx->commit();
494 2
        } catch (Exception $e) {
495 2
            $tx->rollback();
496
497 2
            throw $e;
498
        }
499 4
    }
500
501
    /**
502
     * {@inheritdoc}
503
     */
504 6
    public function removeRootBindingDescriptor(Uuid $uuid)
505
    {
506 6
        $this->assertModulesLoaded();
507
508 6
        if (!$this->bindingDescriptors->contains($uuid)) {
509 1
            return;
510
        }
511
512 5
        $bindingDescriptor = $this->bindingDescriptors->get($uuid);
513
514 5
        if (!$bindingDescriptor->getContainingModule() instanceof RootModule) {
515 1
            return;
516
        }
517
518 4
        $tx = new Transaction();
519
520
        try {
521 4
            $syncOp = $this->syncBindingUuid($uuid);
522 4
            $syncOp->takeSnapshot();
523
524 4
            $tx->execute($this->unloadBindingDescriptor($bindingDescriptor));
525 4
            $tx->execute($syncOp);
526 4
            $tx->execute($this->removeBindingDescriptorFromModuleFile($uuid));
527
528 4
            $this->saveRootModuleFile();
529
530 3
            $tx->commit();
531 1
        } catch (Exception $e) {
532 1
            $tx->rollback();
533
534 1
            throw $e;
535
        }
536 3
    }
537
538
    /**
539
     * {@inheritdoc}
540
     */
541 3 View Code Duplication
    public function removeRootBindingDescriptors(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...
542
    {
543 3
        $this->assertModulesLoaded();
544
545 3
        $tx = new Transaction();
546
547
        try {
548 3
            foreach ($this->getRootBindingDescriptors() as $bindingDescriptor) {
549 3
                if ($expr->evaluate($bindingDescriptor)) {
550 3
                    $syncOp = $this->syncBindingUuid($bindingDescriptor->getUuid());
551 3
                    $syncOp->takeSnapshot();
552
553 3
                    $tx->execute($this->unloadBindingDescriptor($bindingDescriptor));
554 3
                    $tx->execute($syncOp);
555 3
                    $tx->execute($this->removeBindingDescriptorFromModuleFile($bindingDescriptor->getUuid()));
556
                }
557
            }
558
559 3
            $this->saveRootModuleFile();
560
561 2
            $tx->commit();
562 1
        } catch (Exception $e) {
563 1
            $tx->rollback();
564
565 1
            throw $e;
566
        }
567 2
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572 1
    public function clearRootBindingDescriptors()
573
    {
574 1
        $this->removeRootBindingDescriptors(Expr::true());
575 1
    }
576
577
    /**
578
     * {@inheritdoc}
579
     */
580
    public function getRootBindingDescriptor(Uuid $uuid)
581
    {
582
        $binding = $this->getBindingDescriptor($uuid);
583
584
        if (!$binding->getContainingModule() instanceof RootModule) {
585
            throw NoSuchBindingException::forUuidAndModule($uuid, $this->rootModule->getName());
586
        }
587
588
        return $binding;
589
    }
590
591
    /**
592
     * {@inheritdoc}
593
     */
594 3 View Code Duplication
    public function getRootBindingDescriptors()
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...
595
    {
596 3
        $this->assertModulesLoaded();
597
598 3
        $bindings = array();
599
600 3
        foreach ($this->bindingDescriptors->toArray() as $binding) {
601 3
            if ($binding->getContainingModule() instanceof RootModule) {
602 3
                $bindings[] = $binding;
603
            }
604
        }
605
606 3
        return $bindings;
607
    }
608
609
    /**
610
     * {@inheritdoc}
611
     */
612 1 View Code Duplication
    public function findRootBindingDescriptors(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...
613
    {
614 1
        $expr = Expr::method('getContainingModule', Expr::same($this->rootModule))
615 1
            ->andX($expr);
616
617 1
        return $this->findBindingDescriptors($expr);
618
    }
619
620
    /**
621
     * {@inheritdoc}
622
     */
623 1
    public function hasRootBindingDescriptor(Uuid $uuid)
624
    {
625 1
        return $this->hasBindingDescriptor($uuid) && $this->getBindingDescriptor($uuid)->getContainingModule() instanceof RootModule;
626
    }
627
628
    /**
629
     * {@inheritdoc}
630
     */
631 1 View Code Duplication
    public function hasRootBindingDescriptors(Expression $expr = null)
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...
632
    {
633 1
        $expr2 = Expr::method('getContainingModule', Expr::same($this->rootModule));
634
635 1
        if ($expr) {
636 1
            $expr2 = $expr2->andX($expr);
637
        }
638
639 1
        return $this->hasBindingDescriptors($expr2);
640
    }
641
642
    /**
643
     * {@inheritdoc}
644
     */
645 8 View Code Duplication
    public function enableBindingDescriptor(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...
646
    {
647 8
        $this->assertModulesLoaded();
648
649 8
        if (!$this->bindingDescriptors->contains($uuid)) {
650 1
            throw NoSuchBindingException::forUuid($uuid);
651
        }
652
653 7
        $bindingDescriptor = $this->bindingDescriptors->get($uuid);
654 7
        $module = $bindingDescriptor->getContainingModule();
655
656 7
        if ($module instanceof RootModule) {
657 1
            throw NonRootModuleExpectedException::cannotEnableBinding($uuid, $module->getName());
658
        }
659
660 6
        if ($bindingDescriptor->isTypeNotFound()) {
661 1
            throw NoSuchTypeException::forTypeName($bindingDescriptor->getTypeName());
662
        }
663
664 5
        if ($bindingDescriptor->isTypeNotEnabled()) {
665 1
            throw TypeNotEnabledException::forTypeName($bindingDescriptor->getTypeName());
666
        }
667
668 4
        if ($bindingDescriptor->isEnabled()) {
669 1
            return;
670
        }
671
672 3
        $tx = new Transaction();
673
674
        try {
675 3
            $syncOp = $this->syncBindingUuid($uuid);
676 3
            $syncOp->takeSnapshot();
677
678 3
            $tx->execute($this->enableBindingUuid($uuid, $module->getInstallInfo()));
679 3
            $tx->execute($syncOp);
680
681 3
            $this->saveRootModuleFile();
682
683 1
            $tx->commit();
684 2
        } catch (Exception $e) {
685 2
            $tx->rollback();
686
687 2
            throw $e;
688
        }
689 1
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694 8 View Code Duplication
    public function disableBindingDescriptor(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...
695
    {
696 8
        $this->assertModulesLoaded();
697
698 8
        if (!$this->bindingDescriptors->contains($uuid)) {
699 1
            throw NoSuchBindingException::forUuid($uuid);
700
        }
701
702 7
        $bindingDescriptor = $this->bindingDescriptors->get($uuid);
703 7
        $module = $bindingDescriptor->getContainingModule();
704
705 7
        if ($module instanceof RootModule) {
706 1
            throw NonRootModuleExpectedException::cannotDisableBinding($uuid, $module->getName());
707
        }
708
709 6
        if ($bindingDescriptor->isTypeNotFound()) {
710 1
            throw NoSuchTypeException::forTypeName($bindingDescriptor->getTypeName());
711
        }
712
713 5
        if ($bindingDescriptor->isTypeNotEnabled()) {
714 1
            throw TypeNotEnabledException::forTypeName($bindingDescriptor->getTypeName());
715
        }
716
717 4
        if ($bindingDescriptor->isDisabled()) {
718 1
            return;
719
        }
720
721 3
        $tx = new Transaction();
722
723
        try {
724 3
            $syncOp = $this->syncBindingUuid($uuid);
725 3
            $syncOp->takeSnapshot();
726
727 3
            $tx->execute($this->disableBindingUuid($uuid, $module->getInstallInfo()));
728 3
            $tx->execute($syncOp);
729
730 3
            $this->saveRootModuleFile();
731
732 1
            $tx->commit();
733 2
        } catch (Exception $e) {
734 2
            $tx->rollback();
735
736 2
            throw $e;
737
        }
738 1
    }
739
740
    /**
741
     * {@inheritdoc}
742
     */
743 2
    public function removeObsoleteDisabledBindingDescriptors()
744
    {
745 2
        $this->assertModulesLoaded();
746
747 2
        $removedUuidsByModule = array();
748
749
        try {
750 2
            foreach ($this->rootModuleFile->getInstallInfos() as $installInfo) {
751 2
                foreach ($installInfo->getDisabledBindingUuids() as $uuid) {
752 2
                    if (!$this->bindingDescriptors->contains($uuid)) {
753 2
                        $installInfo->removeDisabledBindingUuid($uuid);
754 2
                        $removedUuidsByModule[$installInfo->getModuleName()][] = $uuid;
755
                    }
756
                }
757
            }
758
759 2
            $this->saveRootModuleFile();
760 1
        } catch (Exception $e) {
761 1
            foreach ($removedUuidsByModule as $moduleName => $removedUuids) {
762 1
                $installInfo = $this->rootModuleFile->getInstallInfo($moduleName);
763
764 1
                foreach ($removedUuids as $uuid) {
765 1
                    $installInfo->addDisabledBindingUuid($uuid);
766
                }
767
            }
768
769 1
            throw $e;
770
        }
771 1
    }
772
773
    /**
774
     * {@inheritdoc}
775
     */
776 3
    public function getBindingDescriptor(Uuid $uuid)
777
    {
778 3
        $this->assertModulesLoaded();
779
780 3
        if (!$this->bindingDescriptors->contains($uuid)) {
781 1
            throw NoSuchBindingException::forUuid($uuid);
782
        }
783
784 2
        return $this->bindingDescriptors->get($uuid);
785
    }
786
787
    /**
788
     * {@inheritdoc}
789
     */
790 3
    public function getBindingDescriptors()
791
    {
792 3
        $this->assertModulesLoaded();
793
794 2
        return array_values($this->bindingDescriptors->toArray());
795
    }
796
797
    /**
798
     * {@inheritdoc}
799
     */
800 2 View Code Duplication
    public function findBindingDescriptors(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...
801
    {
802 2
        $this->assertModulesLoaded();
803
804 2
        $descriptors = array();
805
806 2
        foreach ($this->bindingDescriptors->toArray() as $descriptor) {
807 2
            if ($expr->evaluate($descriptor)) {
808 2
                $descriptors[] = $descriptor;
809
            }
810
        }
811
812 2
        return $descriptors;
813
    }
814
815
    /**
816
     * {@inheritdoc}
817
     */
818 2
    public function hasBindingDescriptor(Uuid $uuid)
819
    {
820 2
        $this->assertModulesLoaded();
821
822 2
        return $this->bindingDescriptors->contains($uuid);
823
    }
824
825
    /**
826
     * {@inheritdoc}
827
     */
828 3 View Code Duplication
    public function hasBindingDescriptors(Expression $expr = null)
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...
829
    {
830 3
        $this->assertModulesLoaded();
831
832 3
        if (!$expr) {
833 2
            return !$this->bindingDescriptors->isEmpty();
834
        }
835
836 2
        foreach ($this->bindingDescriptors->toArray() as $bindingDescriptor) {
837 2
            if ($expr->evaluate($bindingDescriptor)) {
838 2
                return true;
839
            }
840
        }
841
842 2
        return false;
843
    }
844
845
    /**
846
     * {@inheritdoc}
847
     */
848 8
    public function buildDiscovery()
849
    {
850 8
        $this->assertModulesLoaded();
851 8
        $this->emitWarningForDuplicateTypes();
852 8
        $this->emitWarningForInvalidBindings();
853
854 8
        if ($this->discovery->hasBindings() || $this->discovery->hasBindingTypes()) {
855 2
            throw new DiscoveryNotEmptyException('The discovery is not empty.');
856
        }
857
858 6
        $tx = new Transaction();
859
860
        try {
861 6
            foreach ($this->typeDescriptors->toArray() as $typeName => $descriptorsByModule) {
862 5
                foreach ($descriptorsByModule as $typeDescriptor) {
863 5
                    if ($typeDescriptor->isEnabled()) {
864 5
                        $tx->execute($this->addBindingType($typeDescriptor));
865
                    }
866
                }
867
            }
868
869 6
            foreach ($this->bindingDescriptors->toArray() as $bindingDescriptor) {
870 5
                if ($bindingDescriptor->isEnabled()) {
871 5
                    $tx->execute($this->addBinding($bindingDescriptor));
872
                }
873
            }
874
875 6
            $tx->commit();
876
        } catch (Exception $e) {
877
            $tx->rollback();
878
879
            throw $e;
880
        }
881 6
    }
882
883
    /**
884
     * {@inheritdoc}
885
     */
886 1
    public function clearDiscovery()
887
    {
888 1
        $this->discovery->removeBindingTypes();
889 1
    }
890
891 93
    private function assertModulesLoaded()
892
    {
893 93
        if (!$this->typeDescriptors) {
894 93
            $this->loadModules();
895
        }
896 92
    }
897
898 6
    private function assertBindingValid(BindingDescriptor $bindingDescriptor)
899
    {
900 6
        if ($bindingDescriptor->isTypeNotFound() || $bindingDescriptor->isTypeNotEnabled()) {
901 2
            return;
902
        }
903
904 4
        foreach ($bindingDescriptor->getLoadErrors() as $exception) {
905 1
            throw $exception;
906
        }
907 3
    }
908
909 93
    private function loadModules()
910
    {
911 93
        $this->typeDescriptors = new BindingTypeDescriptorCollection();
912 93
        $this->bindingDescriptors = new BindingDescriptorCollection();
913
914
        // First load all the types
915 93 View Code Duplication
        foreach ($this->modules as $module) {
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...
916 93
            if (null === $module->getModuleFile()) {
917 1
                continue;
918
            }
919
920 93
            foreach ($module->getModuleFile()->getTypeDescriptors() as $typeDescriptor) {
921 93
                $this->loadTypeDescriptor($typeDescriptor, $module)->execute();
922
            }
923
        }
924
925
        // Then the bindings for the loaded types
926 93
        foreach ($this->modules as $module) {
927 93
            if (null === $module->getModuleFile()) {
928 1
                continue;
929
            }
930
931 93
            foreach ($module->getModuleFile()->getBindingDescriptors() as $bindingDescriptor) {
932
                // This REALLY shouldn't happen
933 48
                if ($this->bindingDescriptors->contains($bindingDescriptor->getUuid())) {
934 1
                    throw DuplicateBindingException::forUuid($bindingDescriptor->getUuid());
935
                }
936
937 93
                $this->loadBindingDescriptor($bindingDescriptor, $module)->execute();
938
            }
939
        }
940 92
    }
941
942 24
    private function emitWarningForDuplicateTypes()
943
    {
944 24
        foreach ($this->typeDescriptors->getTypeNames() as $typeName) {
945 15
            $moduleNames = $this->typeDescriptors->getModuleNames($typeName);
946
947 15
            if (count($moduleNames) > 1) {
948 4
                $lastModuleName = array_pop($moduleNames);
949
950 4
                $this->logger->warning(sprintf(
951
                    'The modules "%s" and "%s" contain type definitions for '.
952 4
                    'the same type "%s". The type has been disabled.',
953 15
                    implode('", "', $moduleNames),
954
                    $lastModuleName,
955
                    $typeName
956
                ));
957
            }
958
        }
959 24
    }
960
961 8
    private function emitWarningForInvalidBindings()
962
    {
963 8
        foreach ($this->bindingDescriptors->toArray() as $binding) {
964 5
            foreach ($binding->getLoadErrors() as $exception) {
965 2
                $this->logger->warning(sprintf(
966 2
                    'The binding "%s" in module "%s" is invalid: %s',
967 2
                    $binding->getUuid()->toString(),
968 2
                    $binding->getContainingModule()->getName(),
969 5
                    $exception->getMessage()
970
                ));
971
            }
972
        }
973 8
    }
974
975 17
    private function getUuidsByTypeName($typeName)
976
    {
977 17
        $uuids = array();
978
979 17
        foreach ($this->bindingDescriptors->getUuids() as $uuid) {
980 5
            if ($typeName === $this->bindingDescriptors->get($uuid)->getTypeName()) {
981 5
                $uuids[$uuid->toString()] = $uuid;
982
            }
983
        }
984
985 17
        return $uuids;
986
    }
987
988 37
    private function saveRootModuleFile()
989
    {
990 37
        $this->jsonStorage->saveRootModuleFile($this->rootModuleFile);
991 26
    }
992
993 4
    private function addTypeDescriptorToModuleFile(BindingTypeDescriptor $typeDescriptor)
994
    {
995 4
        return new AddTypeDescriptorToModuleFile($typeDescriptor, $this->rootModuleFile);
996
    }
997
998 13
    private function removeTypeDescriptorFromModuleFile($typeName)
999
    {
1000 13
        return new RemoveTypeDescriptorFromModuleFile($typeName, $this->rootModuleFile);
1001
    }
1002
1003 74 View Code Duplication
    private function loadTypeDescriptor(BindingTypeDescriptor $typeDescriptor, Module $module)
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...
1004
    {
1005 74
        $typeName = $typeDescriptor->getTypeName();
1006
1007 74
        return new InterceptedOperation(
1008 74
            new LoadTypeDescriptor($typeDescriptor, $module, $this->typeDescriptors),
1009
            array(
1010 74
                new UpdateDuplicateMarksForTypeName($typeName, $this->typeDescriptors),
1011 74
                new ReloadBindingDescriptorsByTypeName($typeName, $this->bindingDescriptors, $this->typeDescriptors),
1012
            )
1013
        );
1014
    }
1015
1016 13 View Code Duplication
    private function unloadTypeDescriptor(BindingTypeDescriptor $typeDescriptor)
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...
1017
    {
1018 13
        $typeName = $typeDescriptor->getTypeName();
1019
1020 13
        return new InterceptedOperation(
1021 13
            new UnloadTypeDescriptor($typeDescriptor, $this->typeDescriptors),
1022
            array(
1023 13
                new UpdateDuplicateMarksForTypeName($typeName, $this->typeDescriptors),
1024 13
                new ReloadBindingDescriptorsByTypeName($typeName, $this->bindingDescriptors, $this->typeDescriptors),
1025
            )
1026
        );
1027
    }
1028
1029 4
    private function addBindingType(BindingTypeDescriptor $typeDescriptor)
1030
    {
1031 4
        return new AddBindingType($typeDescriptor, $this->discovery);
1032
    }
1033
1034 17
    private function syncTypeName($typeName)
1035
    {
1036 17
        return new SyncTypeName($typeName, $this->discovery, $this->typeDescriptors);
1037
    }
1038
1039 5
    private function addBindingDescriptorToModuleFile(BindingDescriptor $bindingDescriptor)
1040
    {
1041 5
        return new AddBindingDescriptorToModuleFile($bindingDescriptor, $this->rootModuleFile);
1042
    }
1043
1044 7
    private function removeBindingDescriptorFromModuleFile(Uuid $uuid)
1045
    {
1046 7
        return new RemoveBindingDescriptorFromModuleFile($uuid, $this->rootModuleFile);
1047
    }
1048
1049 52
    private function loadBindingDescriptor(BindingDescriptor $bindingDescriptor, Module $module)
1050
    {
1051 52
        return new LoadBindingDescriptor($bindingDescriptor, $module, $this->bindingDescriptors, $this->typeDescriptors);
1052
    }
1053
1054 7
    private function unloadBindingDescriptor(BindingDescriptor $bindingDescriptor)
1055
    {
1056 7
        return new UnloadBindingDescriptor($bindingDescriptor, $this->bindingDescriptors);
1057
    }
1058
1059 3 View Code Duplication
    private function enableBindingUuid(Uuid $uuid, InstallInfo $installInfo)
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...
1060
    {
1061 3
        return new InterceptedOperation(
1062 3
            new EnableBindingUuid($uuid, $installInfo),
1063 3
            new ReloadBindingDescriptorsByUuid($uuid, $this->bindingDescriptors, $this->typeDescriptors)
1064
        );
1065
    }
1066
1067 3 View Code Duplication
    private function disableBindingUuid(Uuid $uuid, InstallInfo $installInfo)
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...
1068
    {
1069 3
        return new InterceptedOperation(
1070 3
            new DisableBindingUuid($uuid, $installInfo),
1071 3
            new ReloadBindingDescriptorsByUuid($uuid, $this->bindingDescriptors, $this->typeDescriptors)
1072
        );
1073
    }
1074
1075 2
    private function addBinding(BindingDescriptor $bindingDescriptor)
1076
    {
1077 2
        return new AddBinding($bindingDescriptor, $this->discovery);
1078
    }
1079
1080 24
    private function syncBindingUuid(Uuid $uuid)
1081
    {
1082 24
        return new SyncBindingUuid($uuid, $this->discovery, $this->bindingDescriptors);
1083
    }
1084
}
1085