Passed
Push — master ( ec97b4...705ca1 )
by Dominik
02:29
created

CheckCommand::execute()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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