Test Failed
Pull Request — master (#67)
by
unknown
03:32
created

CheckCommand::buildManualList()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
crap 20
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\GeneratorUtil\ComposeGenerators;
13
use ComposerRequireChecker\JsonLoader;
14
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
15
use PhpParser\ErrorHandler\Collecting as CollectingErrorHandler;
16
use PhpParser\ParserFactory;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Helper\Table;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
class CheckCommand extends Command
25
{
26 3
    protected function configure()
27
    {
28
        $this
29 3
            ->setName('check')
30 3
            ->setDescription('check the defined dependencies against your code')
31 3
            ->addOption(
32 3
                'config-file',
33 3
                null,
34 3
                InputOption::VALUE_REQUIRED,
35 3
                'the config.json file to configure the checking options'
36
            )
37 3
            ->addArgument(
38 3
                'composer-json',
39 3
                InputArgument::OPTIONAL,
40 3
                'the composer.json of your package, that should be checked',
41 3
                './composer.json'
42
            )
43 3
            ->addOption(
44 3
                'ignore-parse-errors',
45 3
                null,
46 3
                InputOption::VALUE_NONE,
47
                'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
48 3
                . ' errors will be thrown'
49
            )
50 3
            ->addOption(
51
                'register-namespace',
52 2
                null,
53
                InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL,
54 2
                'vendor/package:namespace:path as if it was psr4'
55 2
            );
56
    }
57
58 2
    protected function execute(InputInterface $input, OutputInterface $output): int
59 2
    {
60 1
        if (!$output->isQuiet()) {
61
            $output->writeln($this->getApplication()->getLongVersion());
62 1
        }
63
64 1
        $composerJson = realpath($input->getArgument('composer-json'));
65
        if (false === $composerJson) {
66 1
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
67
        }
68 1
        $this->checkJsonFile($composerJson);
69
70 1
        $options = $this->getCheckOptions($input);
71 1
72 1
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
73 1
        $manualRegistered = $this->buildManualList($input, $composerJson);
74
75
        $sourcesASTs = $this->getASTFromFilesLocator($input);
76
77 1
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
78 1
            (new ComposeGenerators())->__invoke(
79
                $getPackageSourceFiles($composerJson),
80
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson, $manualRegistered)
81 1
            )
82 1
        ));
83
84 1
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
85
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
86
        );
87
88 1
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())
89 1
            ->__invoke($sourcesASTs($getPackageSourceFiles($composerJson)));
90 1
91 1
        if (!count($usedSymbols)) {
92 1
            throw new \LogicException('There were no symbols found, please check your configuration.');
93
        }
94
95 1
        $unknownSymbols = array_diff(
96 1
            $usedSymbols,
97 1
            $definedVendorSymbols,
98
            $definedExtensionSymbols,
99
            $options->getSymbolWhitelist()
100
        );
101
102
        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...
103
            $output->writeln("There were no unknown symbols found.");
104
            return 0;
105
        }
106
107
        $output->writeln("The following unknown symbols were found:");
108
        $table = new Table($output);
109
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
110
        $guesser = new DependencyGuesser();
111
        foreach ($unknownSymbols as $unknownSymbol) {
112
            $guessedDependencies = [];
113
            foreach ($guesser($unknownSymbol) as $guessedDependency) {
114
                $guessedDependencies[] = $guessedDependency;
115
            }
116 1
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
117
        }
118 1
        $table->render();
119 1
120 1
        return ((int)(bool)$unknownSymbols);
121
    }
122
123
    private function getCheckOptions(InputInterface $input): Options
124
    {
125
        $fileName = $input->getOption('config-file');
126
        if (!$fileName) {
127
            return new Options();
128
        }
129
        return new Options((new JsonLoader($fileName))->getData());
130
    }
131 1
132
    /**
133
     * @param string $jsonFile
134 1
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
135 1
     * @throws \ComposerRequireChecker\Exception\NotReadableException
136
     * @internal param string $composerJson the path to composer.json
137
     */
138
    private function checkJsonFile(string $jsonFile)
139
    {
140
        // JsonLoader throws an exception if it cannot load the file
141 1
        new JsonLoader($jsonFile);
142
    }
143 1
144 1
    /**
145 1
     * @param InputInterface $input
146
     * @return LocateASTFromFiles
147
     */
148
    private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFiles
149
    {
150
        $errorHandler = $input->getOption('ignore-parse-errors') ? new CollectingErrorHandler() : null;
151
        $sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7), $errorHandler);
152
        return $sourcesASTs;
153
    }
154
155
    /**
156
     * @return array
157
     */
158
    private function buildManualList(InputInterface $input, $composerJson)
159
    {
160
        if(!$input->hasOption('register-namespace')) {
161
            return [];
162
        }
163
        $packageDir = dirname($composerJson);
164
        $namespaces = [];
165
        foreach($input->getOption('register-namespace') as $option) {
166
            if(preg_match('!^([^/:]+(/[^/:]+)+):([^:]+):([^:]+)$!i', $option, $matches)) {
167
                $namespaces[$packageDir . '/vendor/' . $matches[1]][$matches[3]] = $matches[4];
168
            }
169
        }
170
        return $namespaces;
171
    }
172
}
173