CheckCommand::ensureCommandCanBeExecuted()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 10
cc 3
nc 3
nop 0
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Dominikb\ComposerLicenseChecker;
6
7
use Dominikb\ComposerLicenseChecker\Contracts\DependencyLoaderAware;
8
use Dominikb\ComposerLicenseChecker\Contracts\LicenseConstraintAware;
9
use Dominikb\ComposerLicenseChecker\Contracts\LicenseLookupAware;
10
use Dominikb\ComposerLicenseChecker\Exceptions\CommandExecutionException;
11
use Dominikb\ComposerLicenseChecker\Traits\DependencyLoaderAwareTrait;
12
use Dominikb\ComposerLicenseChecker\Traits\LicenseConstraintAwareTrait;
13
use Dominikb\ComposerLicenseChecker\Traits\LicenseLookupAwareTrait;
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Input\InputDefinition;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
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
    /** @var SymfonyStyle */
26
    private $io;
27
28
    protected function configure()
29
    {
30
        $this->setDefinition(new InputDefinition([
31
            new InputOption(
32
                'project-path',
33
                'p',
34
                InputOption::VALUE_OPTIONAL,
35
                'Path to directory of composer.json file',
36
                realpath('.')
37
            ),
38
            new InputOption(
39
                'composer',
40
                'c',
41
                InputOption::VALUE_OPTIONAL,
42
                'Path to composer executable',
43
                'composer'
44
            ),
45
            new InputOption(
46
                'no-dev',
47
                null,
48
                InputOption::VALUE_OPTIONAL,
49
                'Do not include dev dependencies',
50
                'false'
51
            ),
52
            new InputOption(
53
                'allowlist',
54
                'a',
55
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
56
                'Set a license you want to permit for usage'
57
            ),
58
            new InputOption(
59
                'blocklist',
60
                'b',
61
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
62
                'Mark a specific license prohibited for usage'
63
            ),
64
            new InputOption(
65
                'allow',
66
                '',
67
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
68
                'Determine a vendor or package to always be allowed and never trigger violations'
69
            ),
70
        ]));
71
    }
72
73
    public static function getDefaultName(): ?string
74
    {
75
        return 'check';
76
    }
77
78
    /**
79
     * @throws CommandExecutionException
80
     */
81
    public function execute(InputInterface $input, OutputInterface $output): int
82
    {
83
        $this->io = new SymfonyStyle($input, $output);
84
85
        $this->io->title('Reading through dependencies and checking their licenses ...');
86
87
        $this->ensureCommandCanBeExecuted();
88
89
        $dependencies = $this->dependencyLoader->loadDependencies(
0 ignored issues
show
Bug introduced by
The method loadDependencies() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

89
        /** @scrutinizer ignore-call */ 
90
        $dependencies = $this->dependencyLoader->loadDependencies(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
90
            $input->getOption('composer'),
91
            $input->getOption('project-path'),
92
            ($input->getOption('no-dev') ?? 'true') === 'true'
93
        );
94
95
        $this->io->writeln(count($dependencies).' dependencies were found ...');
96
        $this->io->newLine();
97
98
        $violations = $this->determineViolations($dependencies,
99
            $input->getOption('blocklist'),
100
            $input->getOption('allowlist'),
101
            $input->getOption('allow')
102
        );
103
104
        try {
105
            $this->handleViolations($violations);
106
            $this->io->success('Command finished successfully. No violations detected!');
107
        } catch (CommandExecutionException $exception) {
108
            $this->io->error($exception->getMessage());
109
110
            return self::FAILURE;
111
        }
112
113
        return self::SUCCESS;
114
    }
115
116
    /**
117
     * @throws CommandExecutionException
118
     */
119
    private function ensureCommandCanBeExecuted(): void
120
    {
121
        if (! $this->licenseLookup) {
122
            throw new CommandExecutionException('LicenseLookup must be set via setLicenseLookup() before the command can be executed!');
123
        }
124
125
        if (! $this->dependencyLoader) {
126
            throw new CommandExecutionException('DependencyLoader must be set via setDependencyLoader() before the command can be executed!');
127
        }
128
    }
129
130
    private function determineViolations(array $dependencies, array $blocklist, array $allowlist, array $allowed): array
131
    {
132
        $this->licenseConstraintHandler->setBlocklist($blocklist);
0 ignored issues
show
Bug introduced by
The method setBlocklist() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
        $this->licenseConstraintHandler->/** @scrutinizer ignore-call */ 
133
                                         setBlocklist($blocklist);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
133
        $this->licenseConstraintHandler->setAllowlist($allowlist);
134
135
        $this->licenseConstraintHandler->allow(array_map(function ($dependency) {
136
            return new Dependency($dependency);
137
        }, $allowed));
138
139
        return $this->licenseConstraintHandler->detectViolations($dependencies);
140
    }
141
142
    /**
143
     * @param  ConstraintViolation[]  $violations
144
     *
145
     * @throws CommandExecutionException
146
     */
147
    private function handleViolations(array $violations): void
148
    {
149
        $violationsFound = false;
150
151
        foreach ($violations as $violation) {
152
            if ($violation->hasViolators()) {
153
                $this->io->error($violation->getTitle());
154
                $this->reportViolators($violation->getViolators());
155
                $violationsFound = true;
156
            }
157
        }
158
159
        if ($violationsFound) {
160
            throw new CommandExecutionException('Violators found during execution!');
161
        }
162
    }
163
164
    /**
165
     * @param  Dependency[]  $violators
166
     */
167
    private function reportViolators(array $violators): void
168
    {
169
        $byLicense = [];
170
        foreach ($violators as $violator) {
171
            foreach ($violator->getLicenses() as $license) {
172
                if (! isset($byLicense[$license])) {
173
                    $byLicense[$license] = [];
174
                }
175
                $byLicense[$license][] = $violator;
176
            }
177
        }
178
179
        foreach ($byLicense as $license => $violators) {
180
            $violatorNames = array_map(function (Dependency $dependency) {
181
                return sprintf('"%s [%s]"', $dependency->getName(), $dependency->getVersion());
182
            }, $violators);
183
184
            $this->io->title($license);
185
            $this->io->writeln(implode(', ', $violatorNames));
186
        }
187
    }
188
}
189