Passed
Pull Request — master (#32)
by Jonas
02:47
created

CheckCommand::reportViolators()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 12
c 2
b 0
f 1
dl 0
loc 19
ccs 0
cts 13
cp 0
rs 9.8666
cc 4
nc 6
nop 1
crap 20
1
<?php
2
3
namespace Dominikb\ComposerLicenseChecker;
4
5
use Dominikb\ComposerLicenseChecker\Contracts\DependencyLoaderAware;
6
use Dominikb\ComposerLicenseChecker\Contracts\LicenseConstraintAware;
7
use Dominikb\ComposerLicenseChecker\Contracts\LicenseLookupAware;
8
use Dominikb\ComposerLicenseChecker\Exceptions\CommandExecutionException;
9
use Dominikb\ComposerLicenseChecker\Traits\DependencyLoaderAwareTrait;
10
use Dominikb\ComposerLicenseChecker\Traits\LicenseConstraintAwareTrait;
11
use Dominikb\ComposerLicenseChecker\Traits\LicenseLookupAwareTrait;
12
use Symfony\Component\Console\Attribute\AsCommand;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputDefinition;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Logger\ConsoleLogger;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Style\SymfonyStyle;
20
21
class CheckCommand extends Command implements LicenseLookupAware, LicenseConstraintAware, DependencyLoaderAware
22
{
23
    use LicenseLookupAwareTrait, LicenseConstraintAwareTrait, DependencyLoaderAwareTrait;
24
25
    const LINES_BEFORE_DEPENDENCY_VERSIONS = 2;
26
27
    /** @var ConsoleLogger */
28
    private $logger;
29
    /** @var SymfonyStyle */
30
    private $io;
31
32
    protected function configure()
33
    {
34
        $this->setDefinition(new InputDefinition([
35
            new InputOption(
36
                'project-path',
37
                'p',
38
                InputOption::VALUE_OPTIONAL,
39
                'Path to directory of composer.json file',
40
                realpath('.')
41
            ),
42
            new InputOption(
43
                'composer',
44
                'c',
45
                InputOption::VALUE_OPTIONAL,
46
                'Path to composer executable',
47
                realpath('./vendor/bin/composer')
48
            ),
49
            new InputOption(
50
                'allowlist',
51
                'a',
52
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
53
                'Set a license you want to permit for usage'
54
            ),
55
            new InputOption(
56
                'blocklist',
57
                'b',
58
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
59
                'Mark a specific license prohibited for usage'
60
            ),
61
            new InputOption(
62
                'allow',
63
                '',
64
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
65
                'Determine a vendor or package to always be allowed and never trigger violations'
66
            ),
67
        ]));
68
    }
69
70
    public static function getDefaultName(): ?string
71
    {
72
        return 'check';
73
    }
74
75
    /**
76
     * @throws CommandExecutionException
77
     */
78
    public function execute(InputInterface $input, OutputInterface $output): int
79
    {
80
        $this->logger = new ConsoleLogger($output);
81
        $this->io = new SymfonyStyle($input, $output);
82
83
        $this->io->title('Reading through dependencies and checking their licenses ...');
84
85
        $this->ensureCommandCanBeExecuted();
86
87
        $dependencies = $this->dependencyLoader->loadDependencies(
88
            $input->getOption('composer'),
89
            $input->getOption('project-path')
90
        );
91
92
        $this->io->writeln(count($dependencies).' dependencies were found ...');
93
        $this->io->newLine();
94
95
        $violations = $this->determineViolations($dependencies,
96
            $input->getOption('blocklist'),
97
            $input->getOption('allowlist'),
98
            $input->getOption('allow')
99
        );
100
101
        try {
102
            $this->handleViolations($violations);
103
            $this->io->success('Command finished successfully. No violations detected!');
104
        } catch (CommandExecutionException $exception) {
105
            $this->io->error($exception->getMessage());
106
107
            return 1;
108
        }
109
110
        return 0;
111
    }
112
113
    /**
114
     * @throws CommandExecutionException
115
     */
116
    private function ensureCommandCanBeExecuted(): void
117
    {
118
        if (! $this->licenseLookup) {
119
            throw new CommandExecutionException('LicenseLookup must be set via setLicenseLookup() before the command can be executed!');
120
        }
121
122
        if (! $this->dependencyLoader) {
123
            throw new CommandExecutionException('DependencyLoader must be set via setDependencyLoader() before the command can be executed!');
124
        }
125
    }
126
127
    private function determineViolations(array $dependencies, array $blocklist, array $allowlist, array $allowed): array
128
    {
129
        $this->licenseConstraintHandler->setBlocklist($blocklist);
130
        $this->licenseConstraintHandler->setAllowlist($allowlist);
131
132
        $this->licenseConstraintHandler->allow(array_map(function ($dependency) {
133
            return new Dependency($dependency);
134
        }, $allowed));
135
136
        return $this->licenseConstraintHandler->detectViolations($dependencies);
137
    }
138
139
    /**
140
     * @param  ConstraintViolation[]  $violations
141
     *
142
     * @throws CommandExecutionException
143
     */
144
    private function handleViolations(array $violations): void
145
    {
146
        $violationsFound = false;
147
148
        foreach ($violations as $violation) {
149
            if ($violation->hasViolators()) {
150
                $this->io->error($violation->getTitle());
151
                $this->reportViolators($violation->getViolators());
152
                $violationsFound = true;
153
            }
154
        }
155
156
        if ($violationsFound) {
157
            throw new CommandExecutionException('Violators found during execution!');
158
        }
159
    }
160
161
    /**
162
     * @param  Dependency[]  $violators
163
     */
164
    private function reportViolators(array $violators): void
165
    {
166
        $byLicense = [];
167
        foreach ($violators as $violator) {
168
            $license = $violator->getLicenses()[0];
169
170
            if (! isset($byLicense[$license])) {
171
                $byLicense[$license] = [];
172
            }
173
            $byLicense[$license][] = $violator;
174
        }
175
176
        foreach ($byLicense as $license => $violators) {
177
            $violatorNames = array_map(function (Dependency $dependency) {
178
                return sprintf('"%s [%s]"', $dependency->getName(), $dependency->getVersion());
179
            }, $violators);
180
181
            $this->io->title($license);
182
            $this->io->writeln(implode(', ', $violatorNames));
183
        }
184
    }
185
}
186