Completed
Pull Request — master (#31)
by
unknown
02:09
created

CheckCommand::getCheckOptions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 0
cts 11
cp 0
rs 9.2
cc 4
eloc 12
nc 4
nop 1
crap 20
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\JsonLoader;
21
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
22
use PhpParser\ParserFactory;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Helper\Table;
25
use Symfony\Component\Console\Helper\TableCell;
26
use Symfony\Component\Console\Input\InputArgument;
27
use Symfony\Component\Console\Input\InputInterface;
28
use Symfony\Component\Console\Input\InputOption;
29
use Symfony\Component\Console\Output\OutputInterface;
30
31
class CheckCommand extends Command
32
{
33 1
    protected function configure()
34
    {
35
        $this
36 1
            ->setName('check')
37 1
            ->setDescription('check the defined dependencies against your code')
38 1
            ->addOption(
39 1
                'config-file',
40 1
                null,
41 1
                InputOption::VALUE_REQUIRED,
42 1
                'the config.json file to configure the checking options'
43
            )
44 1
            ->addArgument(
45 1
                'composer-json',
46 1
                InputArgument::OPTIONAL,
47 1
                'the composer.json of your package, that should be checked',
48 1
                './composer.json'
49
            )
50
        ;
51 1
    }
52
53
    protected function execute(InputInterface $input, OutputInterface $output) : int
54
    {
55
56
        if(!$output->isQuiet()) {
57
            $output->writeln($this->getApplication()->getLongVersion());
58
        }
59
60
        $composerJson = realpath($input->getArgument('composer-json'));
61
        if(false === $composerJson) {
62
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
63
        }
64
        $this->checkJsonFile($composerJson);
65
66
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
67
68
        $sourcesASTs  = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7));
69
70
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
71
            (new ComposeGenerators())->__invoke(
72
                $getPackageSourceFiles($composerJson),
73
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
74
            )
75
        ));
76
77
        $options = $this->getCheckOptions($input);
78
79
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
80
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
81
        );
82
83
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs($getPackageSourceFiles($composerJson)));
84
85
        $unknownSymbols = array_diff(
86
            $usedSymbols,
87
            $definedVendorSymbols,
88
            $definedExtensionSymbols,
89
            $options->getSymbolWhitelist()
90
        );
91
92
        if (!$unknownSymbols) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unknownSymbols of type string[] 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...
93
            $output->writeln("There were no unknown symbols found.");
94
            return 0;
95
        }
96
97
        $output->writeln("The following unknown symbols were found:");
98
        $table = new Table($output);
99
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
100
        $guesser = new DependencyGuesser();
101
        foreach ($unknownSymbols as $unknownSymbol) {
102
            $guessedDependencies = [];
103
            foreach($guesser($unknownSymbol) as $guessedDependency) {
104
                $guessedDependencies[] = $guessedDependency;
105
            }
106
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
107
        }
108
        $table->render();
109
110
        return ((int) (bool) $unknownSymbols);
111
    }
112
113
    private function getCheckOptions(InputInterface $input) : Options
114
    {
115
        $fileName = $input->getOption('config-file');
116
        if(!$fileName) {
117
            return new Options();
118
        }
119
120
        $loader = new JsonLoader($fileName);
121
        switch ($loader->getErrorCode()) {
122
            case JsonLoader::ERROR_NO_READABLE:
123
                throw new \InvalidArgumentException('unable to read ' . $fileName);
124
            case JsonLoader::ERROR_INVALID_JSON:
125
                throw new \Exception('error parsing the config file: ' . $loader->getErrorMessage());
126
            default:
127
                return new Options($loader->getData());
128
        }
129
    }
130
131
    /**
132
     * @param string $jsonFile
133
     * @throws InvalidInputFileException
134
     * @internal param string $composerJson the path to composer.json
135
     */
136
    private function checkJsonFile(string $jsonFile)
137
    {
138
        $loader = new JsonLoader($jsonFile);
139
        switch ($loader->getErrorCode()) {
140
            case JsonLoader::ERROR_NO_READABLE:
141
                throw new InvalidInputFileException('cannot read ' . $jsonFile);
142
            case JsonLoader::ERROR_INVALID_JSON:
143
                throw new InvalidInputFileException('error parsing ' . $jsonFile . ': ' . $loader->getErrorMessage());
144
            default:
145
                break;
146
        }
147
148
    }
149
150
}