Test Failed
Pull Request — master (#115)
by Cees-Jan
02:27
created

CheckCommand::execute()   C

Complexity

Conditions 13
Paths 70

Size

Total Lines 104
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 14.4968

Importance

Changes 10
Bugs 1 Features 2
Metric Value
eloc 64
c 10
b 1
f 2
dl 0
loc 104
ccs 46
cts 58
cp 0.7931
rs 6.0787
cc 13
nc 70
nop 2
crap 14.4968

How to fix   Long Method    Complexity   

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\LocateDefinedSymbolsFromExtensions;
8
use ComposerRequireChecker\DependencyGuesser\DependencyGuesser;
9
use ComposerRequireChecker\Exception\DependenciesNotInstalledException;
10
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
11
use ComposerRequireChecker\FileLocator\LocateFilesByGlobPattern;
12
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators;
13
use ComposerRequireChecker\JsonLoader;
14
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
15
use PhpParser\ErrorHandler\Collecting as CollectingErrorHandler;
16
use PhpParser\ParserFactory;
17
use Roave\BetterReflection\BetterReflection;
18
use Roave\BetterReflection\Reflector\ClassReflector;
19
use Roave\BetterReflection\Reflector\ConstantReflector;
20
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
21
use Roave\BetterReflection\Reflector\FunctionReflector;
22
use Roave\BetterReflection\SourceLocator\Type\Composer\Factory\Exception\MissingComposerJson;
23
use Roave\BetterReflection\SourceLocator\Type\Composer\Factory\MakeLocatorForComposerJsonAndInstalledJson;
24
use Symfony\Component\Console\Command\Command;
25
use Symfony\Component\Console\Helper\Table;
26
use Symfony\Component\Console\Input\InputArgument;
27
use Symfony\Component\Console\Input\InputInterface;
28 5
use Symfony\Component\Console\Input\InputOption;
29
use Symfony\Component\Console\Output\OutputInterface;
30
use function dirname;
31 5
32 5
class CheckCommand extends Command
33 5
{
34 5
    protected function configure()
35 5
    {
36 5
        $this
37 5
            ->setName('check')
38
            ->setDescription('check the defined dependencies against your code')
39 5
            ->addOption(
40 5
                'config-file',
41 5
                null,
42 5
                InputOption::VALUE_REQUIRED,
43 5
                'the config.json file to configure the checking options'
44
            )
45 5
            ->addArgument(
46 5
                'composer-json',
47 5
                InputArgument::OPTIONAL,
48 5
                'the composer.json of your package, that should be checked',
49
                './composer.json'
50 5
            )
51
            ->addOption(
52 5
                'ignore-parse-errors',
53
                null,
54 4
                InputOption::VALUE_NONE,
55
                'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
56 4
                . ' errors will be thrown'
57 4
            );
58
    }
59
60 4
    protected function execute(InputInterface $input, OutputInterface $output): int
61 4
    {
62 1
        if (!$output->isQuiet()) {
63
            $output->writeln($this->getApplication()->getLongVersion());
64 3
        }
65
66 3
        $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

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

68
            throw new \InvalidArgumentException('file not found: [' . /** @scrutinizer ignore-type */ $input->getArgument('composer-json') . ']');
Loading history...
69 3
        }
70
        $composerData = $this->getComposerData($composerJson);
71 3
72
        $options = $this->getCheckOptions($input);
73 3
74 3
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
75 3
        $getAdditionalSourceFiles = new LocateFilesByGlobPattern();
76 3
77 3
        $sourcesASTs = $this->getASTFromFilesLocator($input);
78 3
79
        $this->verbose("Collecting used symbols... ", $output);
80
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs(
81 3
            (new ComposeGenerators())->__invoke(
82
                $getPackageSourceFiles($composerData, dirname($composerJson)),
83 3
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson))
84 3
            )
85 3
        ));
86
        $this->verbose("found " . count($usedSymbols) . " symbols.", $output, true);
87 3
88
89 3
        $this->verbose("Collecting defined extension symbols... ", $output);
90 3
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
91 3
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
92 3
        );
93 3
        $this->verbose("found " . count($definedExtensionSymbols) . " symbols.", $output, true);
94
95
        try {
96 3
            $locator = new MakeLocatorForComposerJsonAndInstalledJson();
97
            $astLocator = (new BetterReflection())->astLocator();
98 3
            $composerLocator = $locator(dirname($composerJson), $astLocator);
99
            $classReflector = new ClassReflector($composerLocator);
100
            $functionReflector = new FunctionReflector($composerLocator, $classReflector);
101
            $constantReflector = new ConstantReflector($composerLocator, $classReflector);
102 3
        } catch (MissingComposerJson $missingComposerJson) {
103 3
            $message = 'The composer dependencies have not been installed, run composer install/update first';
104 3
            throw new DependenciesNotInstalledException($message);
105 3
        }
106 3
107 3
        $whiteList = $options->getSymbolWhitelist();
108
        $unknownSymbols = [];
109
        foreach ($usedSymbols as $usedSymbol) {
110 3
            if (in_array($usedSymbol, $whiteList)) {
111 3
                continue;
112 3
            }
113
114
            if (in_array($usedSymbol, $definedExtensionSymbols)) {
115
                continue;
116
            }
117
118
            try {
119
                $classReflector->reflect($usedSymbol);
120
121
                continue;
122
            } catch (IdentifierNotFound $ignore) {
123
                // void
124
            }
125
126
            try {
127
                $functionReflector->reflect($usedSymbol);
128
129
                continue;
130
            } catch (IdentifierNotFound $ignore) {
131 3
                // void
132
            }
133 3
134 3
            try {
135 2
                $constantReflector->reflect($usedSymbol);
136
137 1
                continue;
138
            } catch (IdentifierNotFound $ignore) {
139
                // void
140
            }
141
142
            $unknownSymbols[] = $usedSymbol;
143
        }
144
145 3
        if (!$unknownSymbols) {
146
            $output->writeln("There were no unknown symbols found.");
147
            return 0;
148 3
        }
149
150
        $output->writeln("The following unknown symbols were found:");
151
        $table = new Table($output);
152
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
153
        $guesser = new DependencyGuesser($options);
154
        foreach ($unknownSymbols as $unknownSymbol) {
155 3
            $guessedDependencies = [];
156
            foreach ($guesser($unknownSymbol) as $guessedDependency) {
157 3
                $guessedDependencies[] = $guessedDependency;
158 3
            }
159 3
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
160
        }
161
        $table->render();
162
163
        return ((int)(bool)$unknownSymbols);
164
    }
165
166
    private function getCheckOptions(InputInterface $input): Options
167
    {
168 3
        $fileName = $input->getOption('config-file');
169
        if (!$fileName) {
170 3
            return new Options();
171 2
        }
172
        return new Options((new JsonLoader($fileName))->getData());
173
    }
174 1
175 1
    /**
176
     * @param string $jsonFile
177
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
178
     * @throws \ComposerRequireChecker\Exception\NotReadableException
179
     */
180
    private function getComposerData(string $jsonFile): array
181
    {
182
        // JsonLoader throws an exception if it cannot load the file
183
        return (new JsonLoader($jsonFile))->getData();
184
    }
185
186
    /**
187
     * @param InputInterface $input
188
     * @return LocateASTFromFiles
189
     */
190
    private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFiles
191
    {
192
        $errorHandler = $input->getOption('ignore-parse-errors') ? new CollectingErrorHandler() : null;
193
        $sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7), $errorHandler);
194
        return $sourcesASTs;
195
    }
196
197
198
    /**
199
     * @param string $string the message that should be printed
200
     * @param OutputInterface $output the output to log to
201
     * @param bool $newLine if a new line will be started afterwards
202
     */
203
    private function verbose(string $string, OutputInterface $output, bool $newLine = false): void
204
    {
205
        if (!$output->isVerbose()) {
206
            return;
207
        }
208
209
        $output->write($string, $newLine);
210
    }
211
}
212