Completed
Push — master ( c4dd08...7768b0 )
by Matthias
02:25
created

CheckCommand   B

Complexity

Total Complexity 14

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 16

Test Coverage

Coverage 21.21%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
c 1
b 0
f 0
lcom 2
cbo 16
dl 0
loc 120
ccs 14
cts 66
cp 0.2121
rs 8.4614

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 19 1
B execute() 0 59 6
A getCheckOptions() 0 19 4
A checkJsonFile() 0 11 3
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 = realpath($input->getArgument('composer-json'));
60
        if(false === $composerJson) {
61
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
62
        }
63
        $this->checkJsonFile($composerJson);
64
65
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
66
67
        $sourcesASTs  = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7));
68
69
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
70
            (new ComposeGenerators())->__invoke(
71
                $getPackageSourceFiles($composerJson),
72
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
73
            )
74
        ));
75
76
        $options = $this->getCheckOptions($input);
77
78
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
79
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
80
        );
81
82
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs($getPackageSourceFiles($composerJson)));
83
84
        $unknownSymbols = array_diff(
85
            $usedSymbols,
86
            $definedVendorSymbols,
87
            $definedExtensionSymbols,
88
            $options->getSymbolWhitelist()
89
        );
90
91
        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...
92
            $output->writeln("There were no unknown symbols found.");
93
            return 0;
94
        }
95
96
        $output->writeln("The following unknown symbols were found:");
97
        $table = new Table($output);
98
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
99
        $guesser = new DependencyGuesser();
100
        foreach ($unknownSymbols as $unknownSymbol) {
101
            $guessedDependencies = [];
102
            foreach($guesser($unknownSymbol) as $guessedDependency) {
103
                $guessedDependencies[] = $guessedDependency;
104
            }
105
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
106
        }
107
        $table->render();
108
109
        return ((int) (bool) $unknownSymbols);
110
    }
111
112
    private function getCheckOptions(InputInterface $input) : Options
113
    {
114
        $fileName = $input->getOption('config-file');
115
        if(!$fileName) {
116
            return new Options();
117
        }
118
119
        if(!is_readable($fileName)) {
120
            throw new \InvalidArgumentException('unable to read ' . $fileName);
121
        }
122
123
        $jsonData = json_decode(file_get_contents($fileName), true);
124
        if(false === $jsonData) {
125
            throw new \Exception('error parsing the config file: ' . json_last_error_msg());
126
        }
127
128
        return new Options($jsonData);
129
130
    }
131
132
    /**
133
     * @param string $jsonFile
134
     * @throws InvalidInputFileException
135
     * @internal param string $composerJson the path to composer.json
136
     */
137
    private function checkJsonFile(string $jsonFile)
138
    {
139
        if(!is_readable($jsonFile)) {
140
            throw new InvalidInputFileException('cannot read ' . $jsonFile);
141
        }
142
143
        if(false == json_decode(file_get_contents($jsonFile))) {
144
            throw new InvalidInputFileException('error parsing ' . $jsonFile . ': ' . json_last_error_msg());
145
        }
146
147
    }
148
149
}