Completed
Pull Request — master (#39)
by Bernhard
07:08
created

RepositoryManagerImpl::loadPathMappings()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 9
Ratio 40.91 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 9
loc 22
ccs 15
cts 15
cp 1
rs 8.9197
cc 4
eloc 12
nc 3
nop 0
crap 4
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 View Code Duplication
        foreach ($this->packages as $package) {
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 62
            if (null === $package->getPackageFile()) {
484 51
                continue;
485 62
            }
486 62
487
            foreach ($package->getPackageFile()->getPathMappings() as $mapping) {
488
                $this->loadPathMapping($mapping, $package)->execute();
489 62
            }
490 62
        }
491
492 13
        // Scan all paths for conflicts
493
        $this->updateConflicts($this->mappingsByResource->getRepositoryPaths())->execute();
494 13
    }
495
496
    private function addPathMappingToPackageFile(PathMapping $mapping)
497 12
    {
498
        return new AddPathMappingToPackageFile($mapping, $this->rootPackageFile);
499 12
    }
500
501
    private function removePathMappingFromPackageFile($repositoryPath)
502 57
    {
503
        return new RemovePathMappingFromPackageFile($repositoryPath, $this->rootPackageFile);
504 57
    }
505
506
    private function loadPathMapping(PathMapping $mapping, Package $package)
507 12
    {
508
        return new LoadPathMapping($mapping, $package, $this->packages, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
509 12
    }
510
511
    private function unloadPathMapping(PathMapping $mapping)
512 26
    {
513
        return new UnloadPathMapping($mapping, $this->packages, $this->mappings, $this->mappingsByResource, $this->conflictDetector);
514 26
    }
515
516
    private function syncRepositoryPath($repositoryPath)
517 14
    {
518
        return new SyncRepositoryPath($repositoryPath, $this->repo, $this->mappings, $this->overrideGraph);
519 14
    }
520
521
    private function populateRepository()
522 62
    {
523
        return new PopulateRepository($this->repo, $this->mappings, $this->overrideGraph);
524 62
    }
525
526
    private function updateConflicts(array $repositoryPaths = array())
527 13
    {
528
        return new UpdateConflicts($repositoryPaths, $this->conflictDetector, $this->conflicts, $this->mappingsByResource);
529 13
    }
530
531
    private function overrideConflictingPackages(PathMapping $mapping)
532 25
    {
533
        return new OverrideConflictingPackages($mapping, $this->rootPackage, $this->overrideGraph);
534 25
    }
535 19
536
    private function saveRootPackageFile()
537 8
    {
538
        $this->packageFileStorage->saveRootPackageFile($this->rootPackageFile);
539 8
    }
540 2
541 1
    private function removeResolvedConflicts()
542 1
    {
543 8
        foreach ($this->conflicts as $conflictPath => $conflict) {
544 8
            if ($conflict->isResolved()) {
545
                unset($this->conflicts[$conflictPath]);
546 62
            }
547
        }
548 62
    }
549 62
550 62
    private function assertMappingsLoaded()
551 62
    {
552
        if (!$this->overrideGraph) {
553 13
            $this->loadPathMappings();
554
        }
555 13
    }
556
557 13
    private function assertNoLoadErrors(PathMapping $mapping)
558
    {
559 1
        $loadErrors = $mapping->getLoadErrors();
560
561 12
        if (count($loadErrors) > 0) {
562
            // Rethrow first error
563
            throw reset($loadErrors);
564
        }
565
    }
566
}
567