Completed
Pull Request — master (#28)
by Titouan
09:30
created

RepositoryManagerImpl   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 509
Duplicated Lines 18.66 %

Coupling/Cohesion

Components 1
Dependencies 28

Test Coverage

Coverage 100%

Importance

Changes 14
Bugs 1 Features 6
Metric Value
wmc 64
lcom 1
cbo 28
dl 95
loc 509
c 14
b 1
f 6
ccs 214
cts 214
cp 1
rs 1.3043

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A getContext() 0 4 1
A getRepository() 0 4 1
B addRootPathMapping() 0 37 5
B removeRootPathMapping() 0 32 3
B removeRootPathMappings() 29 29 4
A clearRootPathMappings() 0 4 1
A getRootPathMapping() 0 4 1
A findRootPathMappings() 7 7 1
A getRootPathMappings() 15 15 3
A hasRootPathMapping() 0 4 1
A hasRootPathMappings() 10 10 2
A getPathMapping() 0 13 2
A getPathMappings() 0 14 3
A findPathMappings() 16 16 4
A hasPathMapping() 0 9 1
B hasPathMappings() 18 18 5
A getPathConflicts() 0 6 1
A buildRepository() 0 20 4
A clearRepository() 0 4 1
A loadPathMappings() 0 18 3
A addPathMappingToPackageFile() 0 4 1
A removePathMappingFromPackageFile() 0 4 1
A loadPathMapping() 0 4 1
A unloadPathMapping() 0 4 1
A syncRepositoryPath() 0 4 1
A populateRepository() 0 4 1
A updateConflicts() 0 4 1
A overrideConflictingPackages() 0 4 1
A saveRootPackageFile() 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\Package\Package;
20
use Puli\Manager\Api\Package\PackageCollection;
21
use Puli\Manager\Api\Package\RootPackage;
22
use Puli\Manager\Api\Package\RootPackageFile;
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\OverrideGraph;
29
use Puli\Manager\Conflict\PackageConflictDetector;
30
use Puli\Manager\Package\PackageFileStorage;
31
use Puli\Manager\Repository\Mapping\AddPathMappingToPackageFile;
32
use Puli\Manager\Repository\Mapping\ConflictCollection;
33
use Puli\Manager\Repository\Mapping\LoadPathMapping;
34
use Puli\Manager\Repository\Mapping\OverrideConflictingPackages;
35
use Puli\Manager\Repository\Mapping\PathMappingCollection;
36
use Puli\Manager\Repository\Mapping\PopulateRepository;
37
use Puli\Manager\Repository\Mapping\RemovePathMappingFromPackageFile;
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 RootPackage
78
     */
79
    private $rootPackage;
80
81
    /**
82
     * @var RootPackageFile
83
     */
84
    private $rootPackageFile;
85
86
    /**
87
     * @var EditableRepository
88
     */
89
    private $repo;
90
91
    /**
92
     * @var PackageCollection
93
     */
94
    private $packages;
95
96
    /**
97
     * @var PackageFileStorage
98
     */
99
    private $packageFileStorage;
100
101
    /**
102
     * @var OverrideGraph
103
     */
104
    private $overrideGraph;
105
106
    /**
107
     * @var PackageConflictDetector
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 PackageCollection  $packages
132
     * @param PackageFileStorage $packageFileStorage
133
     */
134 64
    public function __construct(ProjectContext $context, EditableRepository $repo, PackageCollection $packages, PackageFileStorage $packageFileStorage)
135
    {
136 64
        $this->context = $context;
137 64
        $this->dispatcher = $context->getEventDispatcher();
138 64
        $this->repo = $repo;
139 64
        $this->config = $context->getConfig();
140 64
        $this->rootDir = $context->getRootDirectory();
141 64
        $this->rootPackage = $packages->getRootPackage();
142 64
        $this->rootPackageFile = $context->getRootPackageFile();
143 64
        $this->packages = $packages;
144 64
        $this->packageFileStorage = $packageFileStorage;
145 64
    }
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->rootPackageFile->hasPathMapping($mapping->getRepositoryPath())) {
173 1
            throw DuplicatePathMappingException::forRepositoryPath($mapping->getRepositoryPath(), $this->rootPackage->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->rootPackage));
183
184 14
            if (!($flags & self::IGNORE_FILE_NOT_FOUND)) {
185 13
                $this->assertNoLoadErrors($mapping);
186 12
            }
187
188 13
            $tx->execute($this->updateConflicts($mapping->listRepositoryPaths()));
189 13
            $tx->execute($this->overrideConflictingPackages($mapping));
190 13
            $tx->execute($this->updateConflicts());
191 13
            $tx->execute($this->addPathMappingToPackageFile($mapping));
192 13
            $tx->execute($syncOp);
193
194 13
            $this->saveRootPackageFile();
195
196 11
            $tx->commit();
197 14
        } 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->rootPackage->getName())) {
214 2
            return;
215
        }
216
217 9
        $mapping = $this->mappings->get($repositoryPath, $this->rootPackage->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->removePathMappingFromPackageFile($repositoryPath));
226 9
            $tx->execute($syncOp);
227
228 9
            $this->saveRootPackageFile();
229
230 6
            $tx->commit();
231 9
        } 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->removePathMappingFromPackageFile($mapping->getRepositoryPath()));
257 3
                    $tx->execute($syncOp);
258 3
                }
259 3
            }
260
261 3
            $this->saveRootPackageFile();
262
263 2
            $tx->commit();
264 3
        } 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->rootPackage->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('getContainingPackage', Expr::same($this->rootPackage))
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
        $rootPackageName = $this->rootPackage->getName();
309
310 4
        foreach ($this->mappings->toArray() as $mappingsByPackage) {
311 4
            if (isset($mappingsByPackage[$rootPackageName])) {
312 4
                $mappings[] = $mappingsByPackage[$rootPackageName];
313 4
            }
314 4
        }
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->rootPackage->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('getContainingPackage', Expr::same($this->rootPackage));
333
334 1
        if ($expr) {
335 1
            $expr2 = $expr2->andX($expr);
336 1
        }
337
338 1
        return $this->hasPathMappings($expr2);
339
    }
340
341
    /**
342
     * {@inheritdoc}
343
     */
344 6
    public function getPathMapping($repositoryPath, $packageName)
345
    {
346 6
        Assert::string($repositoryPath, 'The repository path must be a string. Got: %s');
347 6
        Assert::string($packageName, 'The package name must be a string. Got: %s');
348
349 6
        $this->assertMappingsLoaded();
350
351 6
        if (!$this->mappings->contains($repositoryPath, $packageName)) {
352 4
            throw NoSuchPathMappingException::forRepositoryPathAndPackage($repositoryPath, $packageName);
353
        }
354
355 2
        return $this->mappings->get($repositoryPath, $packageName);
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361 1
    public function getPathMappings()
362
    {
363 1
        $this->assertMappingsLoaded();
364
365 1
        $mappings = array();
366
367 1
        foreach ($this->mappings->toArray() as $mappingsByPackage) {
368 1
            foreach ($mappingsByPackage as $mapping) {
369 1
                $mappings[] = $mapping;
370 1
            }
371 1
        }
372
373 1
        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 $mappingsByPackage) {
386 2
            foreach ($mappingsByPackage as $mapping) {
387 2
                if ($expr->evaluate($mapping)) {
388 2
                    $mappings[] = $mapping;
389 2
                }
390 2
            }
391 2
        }
392
393 2
        return $mappings;
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399 2
    public function hasPathMapping($repositoryPath, $packageName)
400
    {
401 2
        Assert::string($repositoryPath, 'The repository path must be a string. Got: %s');
402 2
        Assert::string($packageName, 'The package name must be a string. Got: %s');
403
404 2
        $this->assertMappingsLoaded();
405
406 2
        return $this->mappings->contains($repositoryPath, $packageName);
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 $mappingsByPackage) {
421 2
            foreach ($mappingsByPackage as $mapping) {
422 2
                if ($expr->evaluate($mapping)) {
423 2
                    return true;
424
                }
425 2
            }
426 2
        }
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 1
        }
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 1
        }
463 14
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 1
    public function clearRepository()
469
    {
470 1
        $this->repo->clear();
471 1
    }
472
473 62
    private function loadPathMappings()
474
    {
475 62
        $this->overrideGraph = OverrideGraph::forPackages($this->packages);
476 62
        $this->conflictDetector = new PackageConflictDetector($this->overrideGraph);
477 62
        $this->mappings = new PathMappingCollection();
478 62
        $this->mappingsByResource = new PathMappingCollection();
479 62
        $this->conflicts = new ConflictCollection();
480
481
        // Load mappings
482 62
        foreach ($this->packages as $package) {
483 62
            foreach ($package->getPackageFile()->getPathMappings() as $mapping) {
484 51
                $this->loadPathMapping($mapping, $package)->execute();
485 62
            }
486 62
        }
487
488
        // Scan all paths for conflicts
489 62
        $this->updateConflicts($this->mappingsByResource->getRepositoryPaths())->execute();
490 62
    }
491
492 13
    private function addPathMappingToPackageFile(PathMapping $mapping)
493
    {
494 13
        return new AddPathMappingToPackageFile($mapping, $this->rootPackageFile);
495
    }
496
497 12
    private function removePathMappingFromPackageFile($repositoryPath)
498
    {
499 12
        return new RemovePathMappingFromPackageFile($repositoryPath, $this->rootPackageFile);
500
    }
501
502 57
    private function loadPathMapping(PathMapping $mapping, Package $package)
503
    {
504 57
        return new LoadPathMapping($mapping, $package, $this->packages, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
505
    }
506
507 12
    private function unloadPathMapping(PathMapping $mapping)
508
    {
509 12
        return new UnloadPathMapping($mapping, $this->packages, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
510
    }
511
512 26
    private function syncRepositoryPath($repositoryPath)
513
    {
514 26
        return new SyncRepositoryPath($repositoryPath, $this->repo, $this->mappings, $this->overrideGraph);
515
    }
516
517 14
    private function populateRepository()
518
    {
519 14
        return new PopulateRepository($this->repo, $this->mappings, $this->overrideGraph);
520
    }
521
522 62
    private function updateConflicts(array $repositoryPaths = array())
523
    {
524 62
        return new UpdateConflicts($repositoryPaths, $this->conflictDetector, $this->conflicts, $this->mappingsByResource);
525
    }
526
527 13
    private function overrideConflictingPackages(PathMapping $mapping)
528
    {
529 13
        return new OverrideConflictingPackages($mapping, $this->rootPackage, $this->overrideGraph);
530
    }
531
532 25
    private function saveRootPackageFile()
533
    {
534 25
        $this->packageFileStorage->saveRootPackageFile($this->rootPackageFile);
535 19
    }
536
537 8
    private function removeResolvedConflicts()
538
    {
539 8
        foreach ($this->conflicts as $conflictPath => $conflict) {
540 2
            if ($conflict->isResolved()) {
541 1
                unset($this->conflicts[$conflictPath]);
542 1
            }
543 8
        }
544 8
    }
545
546 62
    private function assertMappingsLoaded()
547
    {
548 62
        if (!$this->overrideGraph) {
549 62
            $this->loadPathMappings();
550 62
        }
551 62
    }
552
553 13
    private function assertNoLoadErrors(PathMapping $mapping)
554
    {
555 13
        $loadErrors = $mapping->getLoadErrors();
556
557 13
        if (count($loadErrors) > 0) {
558
            // Rethrow first error
559 1
            throw reset($loadErrors);
560
        }
561 12
    }
562
}
563