ViolationFinder   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 155
ccs 63
cts 63
cp 1
rs 10
wmc 13

4 Methods

Rating   Name   Duplication   Size   Complexity  
B determineUnusedCodeViolations() 0 52 7
A find() 0 10 1
A __construct() 0 3 1
A determineLockViolations() 0 47 4
1
<?php
2
/**
3
 * Copyright MediaCT. All rights reserved.
4
 * https://www.mediact.nl
5
 */
6
7
namespace Mediact\DependencyGuard\Violation\Finder;
8
9
use Composer\Composer;
10
use Composer\Package\PackageInterface;
11
use Mediact\DependencyGuard\Candidate\Candidate;
12
use Mediact\DependencyGuard\Candidate\CandidateExtractor;
13
use Mediact\DependencyGuard\Candidate\CandidateExtractorInterface;
14
use Mediact\DependencyGuard\Candidate\CandidateInterface;
15
use Mediact\DependencyGuard\Php\SymbolIterator;
16
use Mediact\DependencyGuard\Php\SymbolIteratorInterface;
17
use Mediact\DependencyGuard\Violation\Violation;
18
use Mediact\DependencyGuard\Violation\ViolationInterface;
19
use Mediact\DependencyGuard\Violation\ViolationIterator;
20
use Mediact\DependencyGuard\Violation\ViolationIteratorInterface;
21
22
class ViolationFinder implements ViolationFinderInterface
23
{
24
    /** @var CandidateExtractorInterface */
25
    private $extractor;
26
27
    /**
28
     * Constructor.
29
     *
30
     * @param CandidateExtractorInterface|null $extractor
31
     */
32 1
    public function __construct(CandidateExtractorInterface $extractor = null)
33
    {
34 1
        $this->extractor = $extractor ?? new CandidateExtractor();
35 1
    }
36
37
    /**
38
     * Find violations for the given Composer instance and symbols.
39
     *
40
     * @param Composer                $composer
41
     * @param SymbolIteratorInterface $symbols
42
     *
43
     * @return ViolationIteratorInterface
44
     */
45 3
    public function find(
46
        Composer $composer,
47
        SymbolIteratorInterface $symbols
48
    ): ViolationIteratorInterface {
49 3
        $candidates = $this->extractor->extract($composer, $symbols);
50
51 3
        return new ViolationIterator(
52 3
            ...array_merge(
53 3
                $this->determineLockViolations($composer, ...$candidates),
54 3
                $this->determineUnusedCodeViolations($composer, ...$candidates)
55
            )
56
        );
57
    }
58
59
    /**
60
     * Get violations for candidates violating the locked state.
61
     *
62
     * @param Composer           $composer
63
     * @param CandidateInterface ...$candidates
64
     *
65
     * @return ViolationInterface[]
66
     */
67 3
    private function determineLockViolations(
68
        Composer $composer,
69
        CandidateInterface ...$candidates
70
    ): array {
71 3
        $violations = [];
72 3
        $lock       = $composer->getLocker()->getLockData();
73
74 3
        $lockedPackages = array_map(
75
            function (array $package) : string {
76 2
                return $package['name'];
77 3
            },
78 3
            $lock['packages'] ?? []
79
        );
80
81 3
        $lockedDevPackages = array_map(
82
            function (array $package) : string {
83 1
                return $package['name'];
84 3
            },
85 3
            $lock['packages-dev'] ?? []
86
        );
87
88 3
        foreach ($candidates as $candidate) {
89 2
            $package = $candidate->getPackage()->getName();
90
91 2
            if (in_array($package, $lockedDevPackages, true)) {
92 1
                $violations[] = new Violation(
93 1
                    sprintf(
94 1
                        'Code base is dependent on dev package %s.',
95 1
                        $package
96
                    ),
97 1
                    $candidate
98
                );
99 1
                continue;
100
            }
101
102 2
            if (!in_array($package, $lockedPackages, true)) {
103 1
                $violations[] = new Violation(
104 1
                    sprintf(
105 1
                        'Package is not installed: %s.',
106 1
                        $package
107
                    ),
108 1
                    $candidate
109
                );
110
            }
111
        }
112
113 3
        return $violations;
114
    }
115
116
    /**
117
     * Determine what packages are installed without having its code be used in
118
     * the given package.
119
     *
120
     * @param Composer           $composer
121
     * @param CandidateInterface ...$candidates
122
     *
123
     * @return ViolationInterface[]
124
     */
125 3
    private function determineUnusedCodeViolations(
126
        Composer $composer,
127
        CandidateInterface ...$candidates
128
    ): array {
129 3
        $repository = $composer->getRepositoryManager()->getLocalRepository();
130 3
        $package    = $composer->getPackage();
131
132 3
        $violations   = [];
133 3
        $installed    = [];
134 3
        $requirements = array_keys($package->getRequires());
135 3
        $packages     = array_map(
136
            function (CandidateInterface $candidate) : string {
137 2
                return $candidate->getPackage()->getName();
138 3
            },
139 3
            $candidates
140
        );
141
142 3
        foreach ($repository->getPackages() as $package) {
143 2
            $installed[$package->getName()] = $package;
144
        }
145
146 3
        foreach ($requirements as $requirement) {
147
            // Platform requirements aren't part of this test.
148 1
            if (strpos($requirement, '/') === false) {
149 1
                continue;
150
            }
151
152
            // The package is never installed.
153 1
            if (!array_key_exists($requirement, $installed)) {
154 1
                continue;
155
            }
156
157
            /** @var PackageInterface $package */
158 1
            $package = $installed[$requirement];
159
160
            // A meta package would not introduce code symbols.
161 1
            if ($package->getType() === 'metapackage') {
162 1
                continue;
163
            }
164
165 1
            if (!in_array($requirement, $packages, true)) {
166 1
                $violations[] = new Violation(
167 1
                    sprintf(
168 1
                        'Package "%s" is installed, but never used.',
169 1
                        $requirement
170
                    ),
171 1
                    new Candidate($package, new SymbolIterator())
172
                );
173
            }
174
        }
175
176 3
        return $violations;
177
    }
178
}
179