CheckCommand::execute()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 75
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 50
CRAP Score 7

Importance

Changes 10
Bugs 1 Features 2
Metric Value
eloc 49
c 10
b 1
f 2
dl 0
loc 75
ccs 50
cts 50
cp 1
rs 8.1793
cc 7
nc 12
nop 2
crap 7

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\Lexer;
18
use PhpParser\ParserFactory;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Helper\Table;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use function dirname;
26
27
class CheckCommand extends Command
28
{
29 10
    protected function configure()
30
    {
31
        $this
32 10
            ->setName('check')
33 10
            ->setDescription('check the defined dependencies against your code')
34 10
            ->addOption(
35 10
                'config-file',
36 10
                null,
37 10
                InputOption::VALUE_REQUIRED,
38 10
                'the config.json file to configure the checking options'
39
            )
40 10
            ->addArgument(
41 10
                'composer-json',
42 10
                InputArgument::OPTIONAL,
43 10
                'the composer.json of your package, that should be checked',
44 10
                './composer.json'
45
            )
46 10
            ->addOption(
47 10
                'ignore-parse-errors',
48 10
                null,
49 10
                InputOption::VALUE_NONE,
50
                'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
51 10
                . ' errors will be thrown'
52
            );
53 10
    }
54
55 9
    protected function execute(InputInterface $input, OutputInterface $output): int
56
    {
57 9
        if (!$output->isQuiet()) {
58 9
            $output->writeln($this->getApplication()->getLongVersion());
59
        }
60
61 9
        $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

61
        $composerJson = realpath(/** @scrutinizer ignore-type */ $input->getArgument('composer-json'));
Loading history...
62 9
        if (false === $composerJson) {
63 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

63
            throw new \InvalidArgumentException('file not found: [' . /** @scrutinizer ignore-type */ $input->getArgument('composer-json') . ']');
Loading history...
64
        }
65 8
        $composerData = $this->getComposerData($composerJson);
66
67 8
        $options = $this->getCheckOptions($input);
68
69 8
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
70 8
        $getAdditionalSourceFiles = new LocateFilesByGlobPattern();
71
72 8
        $sourcesASTs = $this->getASTFromFilesLocator($input);
73
74 8
        $this->verbose("Collecting defined vendor symbols... ", $output);
75 8
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
76 8
            (new ComposeGenerators())->__invoke(
77 8
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson)),
78 8
                $getPackageSourceFiles($composerData, dirname($composerJson)),
79 8
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
80
            )
81
        ));
82 8
        $this->verbose("found " . count($definedVendorSymbols) . " symbols.", $output, true);
83
84 8
        $this->verbose("Collecting defined extension symbols... ", $output);
85 8
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
86 8
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
87
        );
88 8
        $this->verbose("found " . count($definedExtensionSymbols) . " symbols.", $output, true);
89
90 8
        $this->verbose("Collecting used symbols... ", $output);
91 8
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs(
92 8
            (new ComposeGenerators())->__invoke(
93 8
                $getPackageSourceFiles($composerData, dirname($composerJson)),
94 8
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson))
95
            )
96
        ));
97 8
        $this->verbose("found " . count($usedSymbols) . " symbols.", $output, true);
98
99 8
        if (!count($usedSymbols)) {
100 1
            throw new \LogicException('There were no symbols found, please check your configuration.');
101
        }
102
103 7
        $this->verbose("Checking for unknown symbols... ", $output, true);
104 7
        $unknownSymbols = array_diff(
105 7
            $usedSymbols,
106 7
            $definedVendorSymbols,
107 7
            $definedExtensionSymbols,
108 7
            $options->getSymbolWhitelist()
109
        );
110
111 7
        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...
112 5
            $output->writeln("There were no unknown symbols found.");
113 5
            return 0;
114
        }
115
116 2
        $output->writeln("The following unknown symbols were found:");
117 2
        $table = new Table($output);
118 2
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
119 2
        $guesser = new DependencyGuesser($options);
120 2
        foreach ($unknownSymbols as $unknownSymbol) {
121 2
            $guessedDependencies = [];
122 2
            foreach ($guesser($unknownSymbol) as $guessedDependency) {
123 1
                $guessedDependencies[] = $guessedDependency;
124
            }
125 2
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
126
        }
127 2
        $table->render();
128
129 2
        return ((int)(bool)$unknownSymbols);
130
    }
131
132 8
    private function getCheckOptions(InputInterface $input): Options
133
    {
134 8
        $fileName = $input->getOption('config-file');
135 8
        if (!$fileName) {
136 6
            return new Options();
137
        }
138 2
        return new Options((new JsonLoader($fileName))->getData());
139
    }
140
141
    /**
142
     * @param string $jsonFile
143
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
144
     * @throws \ComposerRequireChecker\Exception\NotReadableException
145
     */
146 8
    private function getComposerData(string $jsonFile): array
147
    {
148
        // JsonLoader throws an exception if it cannot load the file
149 8
        return (new JsonLoader($jsonFile))->getData();
150
    }
151
152
    /**
153
     * @param InputInterface $input
154
     * @return LocateASTFromFiles
155
     */
156 8
    private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFiles
157
    {
158 8
        $errorHandler = $input->getOption('ignore-parse-errors') ? new CollectingErrorHandler() : null;
159 8
        $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer());
160 8
        return new LocateASTFromFiles($parser, $errorHandler);
161
    }
162
163
164
    /**
165
     * @param string $string the message that should be printed
166
     * @param OutputInterface $output the output to log to
167
     * @param bool $newLine if a new line will be started afterwards
168
     */
169 8
    private function verbose(string $string, OutputInterface $output, bool $newLine = false): void
170
    {
171 8
        if (!$output->isVerbose()) {
172 7
            return;
173
        }
174
175 1
        $output->write($string, $newLine);
176 1
    }
177
}
178