Passed
Pull Request — master (#117)
by Andreas
07:51 queued 05:09
created

CheckCommand::execute()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 78
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 50
CRAP Score 7.0003

Importance

Changes 10
Bugs 1 Features 2
Metric Value
eloc 50
c 10
b 1
f 2
dl 0
loc 78
ccs 50
cts 51
cp 0.9804
rs 8.1575
cc 7
nc 12
nop 2
crap 7.0003

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ComposerRequireChecker\Cli;
4
5
use ComposerRequireChecker\ASTLocator\LocateASTFromFiles;
6
use ComposerRequireChecker\DefinedExtensionsResolver\DefinedExtensionsResolver;
7
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromASTRoots;
8
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromExtensions;
9
use ComposerRequireChecker\DependencyGuesser\DependencyGuesser;
10
use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles;
11
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
12
use ComposerRequireChecker\FileLocator\LocateFilesByGlobPattern;
13
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators;
14
use ComposerRequireChecker\JsonLoader;
15
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
16
use PhpParser\ErrorHandler\Collecting as CollectingErrorHandler;
17
use PhpParser\ParserFactory;
18
use Symfony\Component\Console\Command\Command;
19
use Symfony\Component\Console\Helper\Table;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use function dirname;
25
26
class CheckCommand extends Command
27
{
28 6
    protected function configure()
29
    {
30
        $this
31 6
            ->setName('check')
32 6
            ->setDescription('check the defined dependencies against your code')
33 6
            ->addOption(
34 6
                'config-file',
35 6
                null,
36 6
                InputOption::VALUE_REQUIRED,
37 6
                'the config.json file to configure the checking options'
38
            )
39 6
            ->addArgument(
40 6
                'composer-json',
41 6
                InputArgument::OPTIONAL,
42 6
                'the composer.json of your package, that should be checked',
43 6
                './composer.json'
44
            )
45 6
            ->addOption(
46 6
                'ignore-parse-errors',
47 6
                null,
48 6
                InputOption::VALUE_NONE,
49
                'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
50 6
                . ' errors will be thrown'
51
            );
52 6
    }
53
54 5
    protected function execute(InputInterface $input, OutputInterface $output): int
55
    {
56 5
        if (!$output->isQuiet()) {
57 5
            $output->writeln($this->getApplication()->getLongVersion());
58
        }
59
60 5
        $composerJson = realpath($input->getArgument('composer-json'));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('composer-json') can also be of type string[]; however, parameter $path of realpath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

60
        $composerJson = realpath(/** @scrutinizer ignore-type */ $input->getArgument('composer-json'));
Loading history...
61 5
        if (false === $composerJson) {
62 1
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
0 ignored issues
show
Bug introduced by
Are you sure $input->getArgument('composer-json') of type null|string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

62
            throw new \InvalidArgumentException('file not found: [' . /** @scrutinizer ignore-type */ $input->getArgument('composer-json') . ']');
Loading history...
63
        }
64 4
        $composerData = $this->getComposerData($composerJson);
65
66 4
        $options = $this->getCheckOptions($input);
67
68 4
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
69 4
        $getAdditionalSourceFiles = new LocateFilesByGlobPattern();
70
71 4
        $sourcesASTs = $this->getASTFromFilesLocator($input);
72
73 4
        $this->verbose("Collecting defined vendor symbols... ", $output);
74 4
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
75 4
            (new ComposeGenerators())->__invoke(
76 4
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson)),
77 4
                $getPackageSourceFiles($composerData, dirname($composerJson)),
78 4
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
79
            )
80
        ));
81 4
        $this->verbose("found " . count($definedVendorSymbols) . " symbols.", $output, true);
82
83 4
        $this->verbose("Collecting defined extension symbols... ", $output);
84 4
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
85 4
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
86
        );
87 4
        $this->verbose("found " . count($definedExtensionSymbols) . " symbols.", $output, true);
88
89 4
        $this->verbose("Collecting used symbols... ", $output);
90 4
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs(
91 4
            (new ComposeGenerators())->__invoke(
92 4
                $getPackageSourceFiles($composerData, dirname($composerJson)),
93 4
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson))
94
            )
95
        ));
96 4
        $this->verbose("found " . count($usedSymbols) . " symbols.", $output, true);
97
98 4
        if (!count($usedSymbols)) {
99
            throw new \LogicException('There were no symbols found, please check your configuration.');
100
        }
101
102 4
        $this->verbose("Checking for unknown symbols... ", $output, true);
103 4
        $unknownSymbols = array_diff(
104 4
            $usedSymbols,
105 4
            $definedVendorSymbols,
106 4
            $definedExtensionSymbols,
107 4
            $options->getSymbolWhitelist()
108
        );
109
110 4
        if (!$unknownSymbols) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unknownSymbols of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
111 3
            $output->writeln("There were no unknown symbols found.");
112 3
            return 0;
113
        }
114
115 1
        $output->writeln("The following unknown symbols were found:");
116 1
        $table = new Table($output);
117 1
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
118 1
        $guesser = new DependencyGuesser($options);
119
120 1
        natcasesort($unknownSymbols);
121
122 1
        foreach ($unknownSymbols as $unknownSymbol) {
123 1
            $guessedDependencies = [];
124 1
            foreach ($guesser($unknownSymbol) as $guessedDependency) {
125 1
                $guessedDependencies[] = $guessedDependency;
126
            }
127 1
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
128
        }
129 1
        $table->render();
130
131 1
        return ((int)(bool)$unknownSymbols);
132
    }
133
134 4
    private function getCheckOptions(InputInterface $input): Options
135
    {
136 4
        $fileName = $input->getOption('config-file');
137 4
        if (!$fileName) {
138 3
            return new Options();
139
        }
140 1
        return new Options((new JsonLoader($fileName))->getData());
141
    }
142
143
    /**
144
     * @param string $jsonFile
145
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
146
     * @throws \ComposerRequireChecker\Exception\NotReadableException
147
     */
148 4
    private function getComposerData(string $jsonFile): array
149
    {
150
        // JsonLoader throws an exception if it cannot load the file
151 4
        return (new JsonLoader($jsonFile))->getData();
152
    }
153
154
    /**
155
     * @param InputInterface $input
156
     * @return LocateASTFromFiles
157
     */
158 4
    private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFiles
159
    {
160 4
        $errorHandler = $input->getOption('ignore-parse-errors') ? new CollectingErrorHandler() : null;
161 4
        $sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7), $errorHandler);
162 4
        return $sourcesASTs;
163
    }
164
165
166
    /**
167
     * @param string $string the message that should be printed
168
     * @param OutputInterface $output the output to log to
169
     * @param bool $newLine if a new line will be started afterwards
170
     */
171 4
    private function verbose(string $string, OutputInterface $output, bool $newLine = false): void
172
    {
173 4
        if (!$output->isVerbose()) {
174 3
            return;
175
        }
176
177 1
        $output->write($string, $newLine);
178 1
    }
179
}
180