Passed
Push — master ( d29b77...fa6b97 )
by Christian
01:54
created

Command::doExecute()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 6.0012

Importance

Changes 0
Metric Value
cc 6
eloc 29
nc 17
nop 0
dl 0
loc 43
ccs 29
cts 30
cp 0.9667
crap 6.0012
rs 8.8337
c 0
b 0
f 0
1
<?php
2
3
namespace Scheb\Tombstone\Analyzer\Cli;
4
5
use Scheb\Tombstone\Analyzer\Analyzer;
6
use Scheb\Tombstone\Analyzer\Config\ConfigurationLoader;
7
use Scheb\Tombstone\Analyzer\Config\YamlConfigProvider;
8
use Scheb\Tombstone\Analyzer\Log\AnalyzerLogDirectoryReader;
9
use Scheb\Tombstone\Analyzer\Log\LogReaderInterface;
10
use Scheb\Tombstone\Analyzer\Matching\MethodNameStrategy;
11
use Scheb\Tombstone\Analyzer\Matching\PositionStrategy;
12
use Scheb\Tombstone\Analyzer\Report\ConsoleReportGenerator;
13
use Scheb\Tombstone\Analyzer\Report\HtmlReportGenerator;
14
use Scheb\Tombstone\Analyzer\Report\PhpReportGenerator;
15
use Scheb\Tombstone\Analyzer\Report\ReportExporter;
16
use Scheb\Tombstone\Analyzer\Source\TombstoneExtractorFactory;
17
use Scheb\Tombstone\Analyzer\Source\TombstoneExtractorInterface;
18
use Scheb\Tombstone\Analyzer\TombstoneIndex;
19
use Scheb\Tombstone\Analyzer\VampireIndex;
20
use SebastianBergmann\FinderFacade\FinderFacade;
21
use Symfony\Component\Console\Command\Command as AbstractCommand;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
26
class Command extends AbstractCommand
27
{
28
    /**
29
     * @var InputInterface
30
     */
31
    private $input;
32
33
    /**
34
     * @var ConsoleOutput
35
     */
36
    private $output;
37
38 1
    protected function configure()
39
    {
40
        $this
41 1
            ->setName('tombstone')
42 1
            ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to config file');
43 1
    }
44
45 1
    protected function execute(InputInterface $input, OutputInterface $output)
46
    {
47 1
        $this->input = $input;
48 1
        $this->output = new ConsoleOutput($output);
49
50
        try {
51 1
            $this->doExecute();
52
        } catch (\Exception $e) {
53
            $output->writeln($e->getMessage());
54
55
            return $e->getCode() ?: 1;
56
        }
57
58 1
        return 0;
59
    }
60
61 1
    private function doExecute(): void
62
    {
63 1
        $configFile = $this->input->getOption('config') ?? getcwd().DIRECTORY_SEPARATOR.'tombstone.yml';
64 1
        if (!file_exists($configFile)) {
0 ignored issues
show
Bug introduced by Christian Scheb
It seems like $configFile can also be of type string[]; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
        if (!file_exists(/** @scrutinizer ignore-type */ $configFile)) {
Loading history...
65
            throw new \InvalidArgumentException('Could not find configuration file '.$configFile);
0 ignored issues
show
Bug introduced by Christian Scheb
Are you sure $configFile of type boolean|string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

65
            throw new \InvalidArgumentException('Could not find configuration file './** @scrutinizer ignore-type */ $configFile);
Loading history...
66
        }
67
68 1
        $this->output->debug('Load config from '.$configFile);
69 1
        $configLoader = ConfigurationLoader::create();
70 1
        $config = $configLoader->loadConfiguration([new YamlConfigProvider($configFile)]);
71 1
        $rootDir = $config['rootDir'];
72
73 1
        $tombstoneIndex = new TombstoneIndex($rootDir);
74 1
        $vampireIndex = new VampireIndex();
75 1
        $tombstoneExtractor = TombstoneExtractorFactory::create($config, $tombstoneIndex, $this->output);
76 1
        $logReaders = $this->createLogReaders($config);
77 1
        $analyzer = $this->createAnalyzer();
78
79 1
        $this->output->writeln('Scan source code ...');
80 1
        $files = $this->collectSourceFiles($config);
81 1
        $this->extractTombstones($files, $tombstoneExtractor);
82
83 1
        $this->output->writeln('Read logs ...');
84 1
        foreach ($logReaders as $logReader) {
85 1
            $logReader->collectVampires($vampireIndex);
86
        }
87
88 1
        $this->output->writeln('Analyze tombstones ...');
89 1
        $result = $analyzer->getResult($tombstoneIndex, $vampireIndex);
90
91 1
        $reportGenerators = [];
92 1
        if ($config['report']['console']) {
93 1
            $reportGenerators[] = new ConsoleReportGenerator($this->output, $rootDir);
94
        }
95 1
        if ($config['report']['html']) {
96 1
            $reportGenerators[] = new HtmlReportGenerator($config['report']['html'], $rootDir);
97
        }
98 1
        if ($config['report']['php']) {
99 1
            $reportGenerators[] = new PhpReportGenerator($config['report']['php']);
100
        }
101
102 1
        $reportExporter = new ReportExporter($this->output, $reportGenerators);
103 1
        $reportExporter->generate($result);
104 1
    }
105
106 1
    private function createAnalyzer(): Analyzer
107
    {
108
        $matchingStrategies = [
109 1
            new MethodNameStrategy(),
110 1
            new PositionStrategy(),
111
        ];
112
113 1
        return new Analyzer($matchingStrategies);
114
    }
115
116
    /**
117
     * @param array $config
118
     * @return LogReaderInterface[]
119
     */
120 1
    private function createLogReaders(array $config): array
121
    {
122 1
        $logReaders = [];
123 1
        if (isset($config['logs']['directory'])) {
124 1
            $logReaders[] = AnalyzerLogDirectoryReader::create($config['logs']['directory'], $this->output);
125
        }
126 1
        if (isset($config['logs']['custom'])) {
127 1
            require_once $config['logs']['custom']['file'];
128 1
            $reflectionClass = new \ReflectionClass($config['logs']['custom']['class']);
129 1
            if (!$reflectionClass->implementsInterface(LogReaderInterface::class)) {
130
                throw new \Exception('Class '.$config['logs']['custom']['class'].' must implement '.LogReaderInterface::class);
131
            }
132
133 1
            $logReaders[] = $reflectionClass->newInstance();
134
        }
135
136 1
        return $logReaders;
137
    }
138
139 1
    private function collectSourceFiles(array $config): array
140
    {
141 1
        $finder = new FinderFacade(
142 1
            $config['source']['directories'],
143 1
            $config['source']['excludes'],
144 1
            $config['source']['names'],
145 1
            $config['source']['notNames']
146
        );
147
148 1
        return $finder->findFiles();
149
    }
150
151 1
    private function extractTombstones(array $files, TombstoneExtractorInterface $tombstoneExtractor): void
152
    {
153 1
        $progress = $this->output->createProgressBar(count($files));
154 1
        foreach ($files as $file) {
155 1
            $this->output->debug($file);
156 1
            $tombstoneExtractor->extractTombstones($file);
157 1
            $progress->advance();
158
        }
159 1
        $this->output->writeln();
160 1
    }
161
}
162