Completed
Pull Request — master (#9)
by Matthias
02:27
created

CheckCommand::execute()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 56
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 56
rs 8.7592
cc 5
eloc 34
nc 8
nop 2

How to fix   Long Method   

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
 * 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
    protected function configure()
33
    {
34
        $this
35
            ->setName('check')
36
            ->setDescription('check the defined dependencies against your code')
37
            ->addOption(
38
                'config-file',
39
                null,
40
                InputOption::VALUE_REQUIRED,
41
                'the config.json file to configure the checking options'
42
            )
43
            ->addArgument(
44
                'composer-json',
45
                InputArgument::OPTIONAL,
46
                'the composer.json of your package, that should be checked',
47
                './composer.json'
48
            )
49
        ;
50
    }
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 = $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)
0 ignored issues
show
Unused Code introduced by
The call to ComposeGenerators::__invoke() has too many arguments starting with (new \ComposerRequireChe...__invoke($composerJson).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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) {
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...
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
}