RepositoryManagerImpl   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 513
Duplicated Lines 20.27 %

Coupling/Cohesion

Components 1
Dependencies 28

Test Coverage

Coverage 100%

Importance

Changes 10
Bugs 1 Features 3
Metric Value
wmc 65
c 10
b 1
f 3
lcom 1
cbo 28
dl 104
loc 513
ccs 196
cts 196
cp 1
rs 1.3043

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getContext() 0 4 1
A getRepository() 0 4 1
A findPathMappings() 16 16 4
A buildRepository() 0 20 4
A loadPathMapping() 0 4 1
B addRootPathMapping() 0 37 5
B removeRootPathMapping() 0 32 3
A getRootPathMappings() 15 15 3
A getPathMapping() 0 13 2
A getPathMappings() 0 14 3
A hasPathMapping() 0 9 1
B hasPathMappings() 18 18 5
B loadPathMappings() 9 22 4
A addPathMappingToModuleFile() 0 4 1
A removePathMappingFromModuleFile() 0 4 1
A overrideConflictingModules() 0 4 1
A saveRootModuleFile() 0 4 1
B removeRootPathMappings() 29 29 4
A __construct() 0 12 1
A clearRootPathMappings() 0 4 1
A getRootPathMapping() 0 4 1
A findRootPathMappings() 7 7 1
A hasRootPathMapping() 0 4 1
A hasRootPathMappings() 10 10 2
A getPathConflicts() 0 6 1
A clearRepository() 0 4 1
A unloadPathMapping() 0 4 1
A syncRepositoryPath() 0 4 1
A populateRepository() 0 4 1
A updateConflicts() 0 4 1
A removeResolvedConflicts() 0 8 3
A assertMappingsLoaded() 0 6 2
A assertNoLoadErrors() 0 9 2

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 RepositoryManagerImpl 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 RepositoryManagerImpl, 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\Repository;
13
14
use Exception;
15
use Puli\Manager\Api\Config\Config;
16
use Puli\Manager\Api\Context\ProjectContext;
17
use Puli\Manager\Api\Event\BuildRepositoryEvent;
18
use Puli\Manager\Api\Event\PuliEvents;
19
use Puli\Manager\Api\Module\Module;
20
use Puli\Manager\Api\Module\ModuleList;
21
use Puli\Manager\Api\Module\RootModule;
22
use Puli\Manager\Api\Module\RootModuleFile;
23
use Puli\Manager\Api\Repository\DuplicatePathMappingException;
24
use Puli\Manager\Api\Repository\NoSuchPathMappingException;
25
use Puli\Manager\Api\Repository\PathMapping;
26
use Puli\Manager\Api\Repository\RepositoryManager;
27
use Puli\Manager\Assert\Assert;
28
use Puli\Manager\Conflict\DependencyGraph;
29
use Puli\Manager\Conflict\ModuleConflictDetector;
30
use Puli\Manager\Json\JsonStorage;
31
use Puli\Manager\Repository\Mapping\AddPathMappingToModuleFile;
32
use Puli\Manager\Repository\Mapping\ConflictCollection;
33
use Puli\Manager\Repository\Mapping\LoadPathMapping;
34
use Puli\Manager\Repository\Mapping\OverrideConflictingModules;
35
use Puli\Manager\Repository\Mapping\PathMappingCollection;
36
use Puli\Manager\Repository\Mapping\PopulateRepository;
37
use Puli\Manager\Repository\Mapping\RemovePathMappingFromModuleFile;
38
use Puli\Manager\Repository\Mapping\SyncRepositoryPath;
39
use Puli\Manager\Repository\Mapping\UnloadPathMapping;
40
use Puli\Manager\Repository\Mapping\UpdateConflicts;
41
use Puli\Manager\Transaction\Transaction;
42
use Puli\Repository\Api\EditableRepository;
43
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
44
use Webmozart\Expression\Expr;
45
use Webmozart\Expression\Expression;
46
47
/**
48
 * Manages the resource repository of a Puli project.
49
 *
50
 * @since  1.0
51
 *
52
 * @author Bernhard Schussek <[email protected]>
53
 */
54
class RepositoryManagerImpl implements RepositoryManager
55
{
56
    /**
57
     * @var ProjectContext
58
     */
59
    private $context;
60
61
    /**
62
     * @var EventDispatcherInterface
63
     */
64
    private $dispatcher;
65
66
    /**
67
     * @var Config
68
     */
69
    private $config;
70
71
    /**
72
     * @var string
73
     */
74
    private $rootDir;
75
76
    /**
77
     * @var RootModule
78
     */
79
    private $rootModule;
80
81
    /**
82
     * @var RootModuleFile
83
     */
84
    private $rootModuleFile;
85
86
    /**
87
     * @var EditableRepository
88
     */
89
    private $repo;
90
91
    /**
92
     * @var ModuleList
93
     */
94
    private $modules;
95
96
    /**
97
     * @var JsonStorage
98
     */
99
    private $jsonStorage;
100
101
    /**
102
     * @var DependencyGraph
103
     */
104
    private $overrideGraph;
105
106
    /**
107
     * @var ModuleConflictDetector
108
     */
109
    private $conflictDetector;
110
111
    /**
112
     * @var PathMappingCollection
113
     */
114
    private $mappings;
115
116
    /**
117
     * @var PathMappingCollection
118
     */
119
    private $mappingsByResource;
120
121
    /**
122
     * @var ConflictCollection
123
     */
124
    private $conflicts;
125
126
    /**
127
     * Creates a repository manager.
128
     *
129
     * @param ProjectContext     $context
130
     * @param EditableRepository $repo
131
     * @param ModuleList         $modules
132
     * @param JsonStorage        $jsonStorage
133
     */
134 65
    public function __construct(ProjectContext $context, EditableRepository $repo, ModuleList $modules, JsonStorage $jsonStorage)
135
    {
136 65
        $this->context = $context;
137 65
        $this->dispatcher = $context->getEventDispatcher();
138 65
        $this->repo = $repo;
139 65
        $this->config = $context->getConfig();
140 65
        $this->rootDir = $context->getRootDirectory();
141 65
        $this->rootModule = $modules->getRootModule();
142 65
        $this->rootModuleFile = $context->getRootModuleFile();
143 65
        $this->modules = $modules;
144 65
        $this->jsonStorage = $jsonStorage;
145 65
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 1
    public function getContext()
151
    {
152 1
        return $this->context;
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158 1
    public function getRepository()
159
    {
160 1
        return $this->repo;
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 15
    public function addRootPathMapping(PathMapping $mapping, $flags = 0)
167
    {
168 15
        Assert::integer($flags, 'The argument $flags must be a boolean.');
169
170 15
        $this->assertMappingsLoaded();
171
172 15
        if (!($flags & self::OVERRIDE) && $this->rootModuleFile->hasPathMapping($mapping->getRepositoryPath())) {
173 1
            throw DuplicatePathMappingException::forRepositoryPath($mapping->getRepositoryPath(), $this->rootModule->getName());
174
        }
175
176 14
        $tx = new Transaction();
177
178
        try {
179 14
            $syncOp = $this->syncRepositoryPath($mapping->getRepositoryPath());
180 14
            $syncOp->takeSnapshot();
181
182 14
            $tx->execute($this->loadPathMapping($mapping, $this->rootModule));
183
184 14
            if (!($flags & self::IGNORE_FILE_NOT_FOUND)) {
185 13
                $this->assertNoLoadErrors($mapping);
186
            }
187
188 13
            $tx->execute($this->updateConflicts($mapping->listRepositoryPaths()));
189 13
            $tx->execute($this->overrideConflictingModules($mapping));
190 13
            $tx->execute($this->updateConflicts());
191 13
            $tx->execute($this->addPathMappingToModuleFile($mapping));
192 13
            $tx->execute($syncOp);
193
194 13
            $this->saveRootModuleFile();
195
196 11
            $tx->commit();
197 3
        } catch (Exception $e) {
198 3
            $tx->rollback();
199
200 3
            throw $e;
201
        }
202 11
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 11
    public function removeRootPathMapping($repositoryPath)
208
    {
209 11
        Assert::path($repositoryPath);
210
211 11
        $this->assertMappingsLoaded();
212
213 11
        if (!$this->mappings->contains($repositoryPath, $this->rootModule->getName())) {
214 2
            return;
215
        }
216
217 9
        $mapping = $this->mappings->get($repositoryPath, $this->rootModule->getName());
218 9
        $tx = new Transaction();
219
220
        try {
221 9
            $syncOp = $this->syncRepositoryPath($repositoryPath);
222 9
            $syncOp->takeSnapshot();
223
224 9
            $tx->execute($this->unloadPathMapping($mapping));
225 9
            $tx->execute($this->removePathMappingFromModuleFile($repositoryPath));
226 9
            $tx->execute($syncOp);
227
228 9
            $this->saveRootModuleFile();
229
230 6
            $tx->commit();
231 3
        } catch (Exception $e) {
232 3
            $tx->rollback();
233
234 3
            throw $e;
235
        }
236
237 6
        $this->removeResolvedConflicts();
238 6
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 3 View Code Duplication
    public function removeRootPathMappings(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...
244
    {
245 3
        $this->assertMappingsLoaded();
246
247 3
        $tx = new Transaction();
248
249
        try {
250 3
            foreach ($this->getRootPathMappings() as $mapping) {
251 3
                if ($expr->evaluate($mapping)) {
252 3
                    $syncOp = $this->syncRepositoryPath($mapping->getRepositoryPath());
253 3
                    $syncOp->takeSnapshot();
254
255 3
                    $tx->execute($this->unloadPathMapping($mapping));
256 3
                    $tx->execute($this->removePathMappingFromModuleFile($mapping->getRepositoryPath()));
257 3
                    $tx->execute($syncOp);
258
                }
259
            }
260
261 3
            $this->saveRootModuleFile();
262
263 2
            $tx->commit();
264 1
        } catch (Exception $e) {
265 1
            $tx->rollback();
266
267 1
            throw $e;
268
        }
269
270 2
        $this->removeResolvedConflicts();
271 2
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276 1
    public function clearRootPathMappings()
277
    {
278 1
        $this->removeRootPathMappings(Expr::true());
279 1
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 3
    public function getRootPathMapping($repositoryPath)
285
    {
286 3
        return $this->getPathMapping($repositoryPath, $this->rootModule->getName());
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 1 View Code Duplication
    public function findRootPathMappings(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...
293
    {
294 1
        $expr = Expr::method('getContainingModule', Expr::same($this->rootModule))
295 1
            ->andX($expr);
296
297 1
        return $this->findPathMappings($expr);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303 4 View Code Duplication
    public function getRootPathMappings()
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...
304
    {
305 4
        $this->assertMappingsLoaded();
306
307 4
        $mappings = array();
308 4
        $rootModuleName = $this->rootModule->getName();
309
310 4
        foreach ($this->mappings->toArray() as $mappingsByModule) {
311 4
            if (isset($mappingsByModule[$rootModuleName])) {
312 4
                $mappings[] = $mappingsByModule[$rootModuleName];
313
            }
314
        }
315
316 4
        return $mappings;
317
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322 1
    public function hasRootPathMapping($repositoryPath)
323
    {
324 1
        return $this->hasPathMapping($repositoryPath, $this->rootModule->getName());
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330 1 View Code Duplication
    public function hasRootPathMappings(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...
331
    {
332 1
        $expr2 = Expr::method('getContainingModule', Expr::same($this->rootModule));
333
334 1
        if ($expr) {
335 1
            $expr2 = $expr2->andX($expr);
336
        }
337
338 1
        return $this->hasPathMappings($expr2);
339
    }
340
341
    /**
342
     * {@inheritdoc}
343
     */
344 6
    public function getPathMapping($repositoryPath, $moduleName)
345
    {
346 6
        Assert::string($repositoryPath, 'The repository path must be a string. Got: %s');
347 6
        Assert::string($moduleName, 'The module name must be a string. Got: %s');
348
349 6
        $this->assertMappingsLoaded();
350
351 6
        if (!$this->mappings->contains($repositoryPath, $moduleName)) {
352 4
            throw NoSuchPathMappingException::forRepositoryPathAndModule($repositoryPath, $moduleName);
353
        }
354
355 2
        return $this->mappings->get($repositoryPath, $moduleName);
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361 2
    public function getPathMappings()
362
    {
363 2
        $this->assertMappingsLoaded();
364
365 2
        $mappings = array();
366
367 2
        foreach ($this->mappings->toArray() as $mappingsByModule) {
368 1
            foreach ($mappingsByModule as $mapping) {
369 1
                $mappings[] = $mapping;
370
            }
371
        }
372
373 2
        return $mappings;
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379 2 View Code Duplication
    public function findPathMappings(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...
380
    {
381 2
        $this->assertMappingsLoaded();
382
383 2
        $mappings = array();
384
385 2
        foreach ($this->mappings->toArray() as $mappingsByModule) {
386 2
            foreach ($mappingsByModule as $mapping) {
387 2
                if ($expr->evaluate($mapping)) {
388 2
                    $mappings[] = $mapping;
389
                }
390
            }
391
        }
392
393 2
        return $mappings;
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399 2
    public function hasPathMapping($repositoryPath, $moduleName)
400
    {
401 2
        Assert::string($repositoryPath, 'The repository path must be a string. Got: %s');
402 2
        Assert::string($moduleName, 'The module name must be a string. Got: %s');
403
404 2
        $this->assertMappingsLoaded();
405
406 2
        return $this->mappings->contains($repositoryPath, $moduleName);
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412 3 View Code Duplication
    public function hasPathMappings(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...
413
    {
414 3
        $this->assertMappingsLoaded();
415
416 3
        if (!$expr) {
417 2
            return !$this->mappings->isEmpty();
418
        }
419
420 2
        foreach ($this->mappings->toArray() as $mappingsByModule) {
421 2
            foreach ($mappingsByModule as $mapping) {
422 2
                if ($expr->evaluate($mapping)) {
423 2
                    return true;
424
                }
425
            }
426
        }
427
428 2
        return false;
429
    }
430
431
    /**
432
     * {@inheritdoc}
433
     */
434 10
    public function getPathConflicts()
435
    {
436 10
        $this->assertMappingsLoaded();
437
438 10
        return array_values($this->conflicts->toArray());
439
    }
440
441
    /**
442
     * {@inheritdoc}
443
     */
444 15
    public function buildRepository()
445
    {
446 15
        $this->assertMappingsLoaded();
447
448 15
        if ($this->dispatcher->hasListeners(PuliEvents::PRE_BUILD_REPOSITORY)) {
449 2
            $event = new BuildRepositoryEvent($this);
450
451 2
            $this->dispatcher->dispatch(PuliEvents::PRE_BUILD_REPOSITORY, $event);
452
453 2
            if ($event->isBuildSkipped()) {
454 1
                return;
455
            }
456
        }
457
458 14
        $this->populateRepository()->execute();
459
460 14
        if ($this->dispatcher->hasListeners(PuliEvents::POST_BUILD_REPOSITORY)) {
461 1
            $this->dispatcher->dispatch(PuliEvents::POST_BUILD_REPOSITORY, new BuildRepositoryEvent($this));
462
        }
463 14
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 1
    public function clearRepository()
469
    {
470 1
        $this->repo->clear();
471 1
    }
472
473 63
    private function loadPathMappings()
474
    {
475 63
        $this->overrideGraph = DependencyGraph::forModules($this->modules);
476 63
        $this->conflictDetector = new ModuleConflictDetector($this->overrideGraph);
477 63
        $this->mappings = new PathMappingCollection();
478 63
        $this->mappingsByResource = new PathMappingCollection();
479 63
        $this->conflicts = new ConflictCollection();
480
481
        // Load mappings
482 63 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...
483 63
            if (null === $module->getModuleFile()) {
484 1
                continue;
485
            }
486
487 63
            foreach ($module->getModuleFile()->getPathMappings() as $mapping) {
488 63
                $this->loadPathMapping($mapping, $module)->execute();
489
            }
490
        }
491
492
        // Scan all paths for conflicts
493 63
        $this->updateConflicts($this->mappingsByResource->getRepositoryPaths())->execute();
494 63
    }
495
496 13
    private function addPathMappingToModuleFile(PathMapping $mapping)
497
    {
498 13
        return new AddPathMappingToModuleFile($mapping, $this->rootModuleFile);
499
    }
500
501 12
    private function removePathMappingFromModuleFile($repositoryPath)
502
    {
503 12
        return new RemovePathMappingFromModuleFile($repositoryPath, $this->rootModuleFile);
504
    }
505
506 57
    private function loadPathMapping(PathMapping $mapping, Module $module)
507
    {
508 57
        return new LoadPathMapping($mapping, $module, $this->modules, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
509
    }
510
511 12
    private function unloadPathMapping(PathMapping $mapping)
512
    {
513 12
        return new UnloadPathMapping($mapping, $this->modules, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
514
    }
515
516 26
    private function syncRepositoryPath($repositoryPath)
517
    {
518 26
        return new SyncRepositoryPath($repositoryPath, $this->repo, $this->mappings, $this->overrideGraph);
519
    }
520
521 14
    private function populateRepository()
522
    {
523 14
        return new PopulateRepository($this->repo, $this->mappings, $this->overrideGraph);
524
    }
525
526 63
    private function updateConflicts(array $repositoryPaths = array())
527
    {
528 63
        return new UpdateConflicts($repositoryPaths, $this->conflictDetector, $this->conflicts, $this->mappingsByResource);
529
    }
530
531 13
    private function overrideConflictingModules(PathMapping $mapping)
532
    {
533 13
        return new OverrideConflictingModules($mapping, $this->rootModule, $this->overrideGraph);
534
    }
535
536 25
    private function saveRootModuleFile()
537
    {
538 25
        $this->jsonStorage->saveRootModuleFile($this->rootModuleFile);
539 19
    }
540
541 8
    private function removeResolvedConflicts()
542
    {
543 8
        foreach ($this->conflicts as $conflictPath => $conflict) {
544 2
            if ($conflict->isResolved()) {
545 2
                unset($this->conflicts[$conflictPath]);
546
            }
547
        }
548 8
    }
549
550 63
    private function assertMappingsLoaded()
551
    {
552 63
        if (!$this->overrideGraph) {
553 63
            $this->loadPathMappings();
554
        }
555 63
    }
556
557 13
    private function assertNoLoadErrors(PathMapping $mapping)
558
    {
559 13
        $loadErrors = $mapping->getLoadErrors();
560
561 13
        if (count($loadErrors) > 0) {
562
            // Rethrow first error
563 1
            throw reset($loadErrors);
564
        }
565 12
    }
566
}
567