1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: matthias |
5
|
|
|
* Date: 28.11.15 |
6
|
|
|
* Time: 16:16 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace ComposerRequireChecker\Cli; |
10
|
|
|
|
11
|
|
|
use ComposerRequireChecker\ASTLocator\LocateASTFromFiles; |
12
|
|
|
use ComposerRequireChecker\DefinedExtensionsResolver\DefinedExtensionsResolver; |
13
|
|
|
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromASTRoots; |
14
|
|
|
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromExtensions; |
15
|
|
|
use ComposerRequireChecker\DependencyGuesser\DependencyGuesser; |
16
|
|
|
use ComposerRequireChecker\Exception\InvalidInputFileException; |
17
|
|
|
use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles; |
18
|
|
|
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles; |
19
|
|
|
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators; |
20
|
|
|
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots; |
21
|
|
|
use PhpParser\ParserFactory; |
22
|
|
|
use Symfony\Component\Console\Command\Command; |
23
|
|
|
use Symfony\Component\Console\Helper\Table; |
24
|
|
|
use Symfony\Component\Console\Helper\TableCell; |
25
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
26
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
27
|
|
|
use Symfony\Component\Console\Input\InputOption; |
28
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
29
|
|
|
|
30
|
|
|
class CheckCommand extends Command |
31
|
|
|
{ |
32
|
1 |
|
protected function configure() |
33
|
|
|
{ |
34
|
|
|
$this |
35
|
1 |
|
->setName('check') |
36
|
1 |
|
->setDescription('check the defined dependencies against your code') |
37
|
1 |
|
->addOption( |
38
|
1 |
|
'config-file', |
39
|
1 |
|
null, |
40
|
1 |
|
InputOption::VALUE_REQUIRED, |
41
|
1 |
|
'the config.json file to configure the checking options' |
42
|
|
|
) |
43
|
1 |
|
->addArgument( |
44
|
1 |
|
'composer-json', |
45
|
1 |
|
InputArgument::OPTIONAL, |
46
|
1 |
|
'the composer.json of your package, that should be checked', |
47
|
1 |
|
'./composer.json' |
48
|
|
|
) |
49
|
|
|
; |
50
|
1 |
|
} |
51
|
|
|
|
52
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) : int |
53
|
|
|
{ |
54
|
|
|
|
55
|
|
|
if(!$output->isQuiet()) { |
56
|
|
|
$output->writeln($this->getApplication()->getLongVersion()); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
$composerJson = getcwd() . '/' . $input->getArgument('composer-json'); |
60
|
|
|
$this->checkJsonFile($composerJson); |
61
|
|
|
|
62
|
|
|
$getPackageSourceFiles = new LocateComposerPackageSourceFiles(); |
63
|
|
|
|
64
|
|
|
$sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7)); |
65
|
|
|
|
66
|
|
|
$definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs( |
67
|
|
|
(new ComposeGenerators())->__invoke( |
68
|
|
|
$getPackageSourceFiles($composerJson), |
69
|
|
|
(new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson) |
70
|
|
|
) |
71
|
|
|
)); |
72
|
|
|
|
73
|
|
|
$options = $this->getCheckOptions($input); |
74
|
|
|
|
75
|
|
|
$definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke( |
76
|
|
|
(new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions()) |
77
|
|
|
); |
78
|
|
|
|
79
|
|
|
$usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs($getPackageSourceFiles($composerJson))); |
80
|
|
|
|
81
|
|
|
$unknownSymbols = array_diff( |
82
|
|
|
$usedSymbols, |
83
|
|
|
$definedVendorSymbols, |
84
|
|
|
$definedExtensionSymbols, |
85
|
|
|
$options->getSymbolWhitelist() |
86
|
|
|
); |
87
|
|
|
|
88
|
|
|
if (!$unknownSymbols) { |
|
|
|
|
89
|
|
|
$output->writeln("There were no unknown symbols found."); |
90
|
|
|
return 0; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$output->writeln("The following unknown symbols were found:"); |
94
|
|
|
$table = new Table($output); |
95
|
|
|
$table->setHeaders(['unknown symbol', 'guessed dependency']); |
96
|
|
|
$guesser = new DependencyGuesser(); |
97
|
|
|
foreach ($unknownSymbols as $unknownSymbol) { |
98
|
|
|
$guessedDependencies = []; |
99
|
|
|
foreach($guesser($unknownSymbol) as $guessedDependency) { |
100
|
|
|
$guessedDependencies[] = $guessedDependency; |
101
|
|
|
} |
102
|
|
|
$table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]); |
103
|
|
|
} |
104
|
|
|
$table->render(); |
105
|
|
|
|
106
|
|
|
return ((int) (bool) $unknownSymbols); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
private function getCheckOptions(InputInterface $input) : Options |
110
|
|
|
{ |
111
|
|
|
$fileName = $input->getOption('config-file'); |
112
|
|
|
if(!$fileName) { |
113
|
|
|
return new Options(); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
if(!is_readable($fileName)) { |
117
|
|
|
throw new \InvalidArgumentException('unable to read ' . $fileName); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$jsonData = json_decode(file_get_contents($fileName), true); |
121
|
|
|
if(false === $jsonData) { |
122
|
|
|
throw new \Exception('error parsing the config file: ' . json_last_error_msg()); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return new Options($jsonData); |
126
|
|
|
|
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param string $jsonFile |
131
|
|
|
* @throws InvalidInputFileException |
132
|
|
|
* @internal param string $composerJson the path to composer.json |
133
|
|
|
*/ |
134
|
|
|
private function checkJsonFile(string $jsonFile) |
135
|
|
|
{ |
136
|
|
|
if(!is_readable($jsonFile)) { |
137
|
|
|
throw new InvalidInputFileException('cannot read ' . $jsonFile); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
if(false == json_decode(file_get_contents($jsonFile))) { |
141
|
|
|
throw new InvalidInputFileException('error parsing ' . $jsonFile . ': ' . json_last_error_msg()); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
} |
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.