1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Koriit\PHPDeps\Commands; |
4
|
|
|
|
5
|
|
|
use Koriit\PHPDeps\Config\Exceptions\InvalidConfig; |
6
|
|
|
use Koriit\PHPDeps\Config\Exceptions\InvalidSchema; |
7
|
|
|
use Koriit\PHPDeps\Console\GraphWriter; |
8
|
|
|
use Koriit\PHPDeps\ExitCodes; |
9
|
|
|
use Koriit\PHPDeps\Helpers\InputHelper; |
10
|
|
|
use Koriit\PHPDeps\Helpers\ModulesHelper; |
11
|
|
|
use Koriit\PHPDeps\Modules\Module; |
12
|
|
|
use Koriit\PHPDeps\Modules\ModuleReader; |
13
|
|
|
use Koriit\PHPDeps\Tokenizer\Exceptions\MalformedFile; |
14
|
|
|
use Symfony\Component\Console\Command\Command; |
15
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
16
|
|
|
use Symfony\Component\Console\Input\InputOption; |
17
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
18
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle; |
19
|
|
|
|
20
|
|
|
class CheckCommand extends Command |
21
|
|
|
{ |
22
|
|
|
/** @var ModuleReader */ |
23
|
|
|
private $modulesReader; |
24
|
|
|
|
25
|
|
|
/** @var GraphWriter */ |
26
|
|
|
private $graphWriter; |
27
|
|
|
|
28
|
|
|
/** @var ModulesHelper */ |
29
|
|
|
private $modulesHelper; |
30
|
|
|
|
31
|
|
|
/** @var InputHelper */ |
32
|
|
|
private $inputHelper; |
33
|
|
|
|
34
|
|
View Code Duplication |
public function __construct(ModulesHelper $modulesHelper, InputHelper $inputHelper, ModuleReader $modulesReader, GraphWriter $graphWriter) |
|
|
|
|
35
|
|
|
{ |
36
|
|
|
parent::__construct(); |
37
|
|
|
|
38
|
|
|
$this->modulesReader = $modulesReader; |
39
|
|
|
$this->graphWriter = $graphWriter; |
40
|
|
|
$this->modulesHelper = $modulesHelper; |
41
|
|
|
$this->inputHelper = $inputHelper; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
protected function configure() |
45
|
|
|
{ |
46
|
|
|
$this |
47
|
|
|
->setName('check') |
48
|
|
|
->setDescription('Check whether there are circular dependencies in modules.') |
49
|
|
|
->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Custom location of configuration file', './phpdeps.xml') |
50
|
|
|
->addOption('graphs', 'g', InputOption::VALUE_NONE, 'Whether to display dependency cycles as graphs(assumed with -v)'); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param InputInterface $input |
55
|
|
|
* @param OutputInterface $output |
56
|
|
|
* |
57
|
|
|
* @throws InvalidConfig |
58
|
|
|
* @throws InvalidSchema |
59
|
|
|
* @throws MalformedFile |
60
|
|
|
* |
61
|
|
|
* @return int Exit code |
62
|
|
|
*/ |
63
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
64
|
|
|
{ |
65
|
|
|
$io = new SymfonyStyle($input, $output); |
66
|
|
|
|
67
|
|
|
$config = $this->inputHelper->readConfig($input); |
68
|
|
|
$drawGraphs = $output->isVerbose() || $input->getOption('graphs'); |
69
|
|
|
|
70
|
|
|
$modules = $this->modulesHelper->findModules($config); |
71
|
|
|
if (!$this->modulesHelper->validateModules($modules, $io)) { |
72
|
|
|
return ExitCodes::UNEXPECTED_ERROR; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$dependencyCycles = $this->findDependencyCycles($modules); |
76
|
|
|
|
77
|
|
|
if (empty($dependencyCycles)) { |
78
|
|
|
$io->success('There are no circular dependencies in your modules!'); |
79
|
|
|
|
80
|
|
|
return ExitCodes::OK; |
81
|
|
|
} else { |
82
|
|
|
$io->warning('There are circular dependencies in your modules!'); |
83
|
|
|
|
84
|
|
|
$this->displayDependencyCycles($dependencyCycles, $io, $drawGraphs); |
85
|
|
|
|
86
|
|
|
return ExitCodes::CIRCULAR_DEPENDENCIES_EXIST; |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param Module[][] $dependencyCycles |
92
|
|
|
* @param SymfonyStyle $io |
93
|
|
|
* @param bool $drawGraphs |
94
|
|
|
*/ |
95
|
|
|
private function displayDependencyCycles(array $dependencyCycles, SymfonyStyle $io, $drawGraphs) |
96
|
|
|
{ |
97
|
|
|
$cyclesCount = \count($dependencyCycles); |
98
|
|
|
$io->writeln('In total there ' . ($cyclesCount > 1 ? 'are ' . $cyclesCount . ' dependency cycles' : 'is 1 dependency cycle') . ' in your modules.'); |
99
|
|
|
|
100
|
|
|
$i = 1; |
101
|
|
|
foreach ($dependencyCycles as $cycle) { |
102
|
|
|
$graphNodes = []; |
103
|
|
|
$out = $i++ . '. '; |
104
|
|
|
|
105
|
|
|
foreach ($cycle as $module) { |
106
|
|
|
$out .= '<fg=white>' . $module->getName() . '</> -> '; |
107
|
|
|
$graphNodes[] = $module->getName() . ' [<fg=magenta>' . $module->getNamespace() . '</>]'; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
$io->section($out . '<fg=white>' . $cycle[0]->getName() . '</>'); |
111
|
|
|
if ($drawGraphs) { |
112
|
|
|
$this->graphWriter->drawGraphCycle($graphNodes); |
113
|
|
|
if ($i <= $cyclesCount) { |
114
|
|
|
$io->newLine(); |
115
|
|
|
$io->newLine(); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @param Module[] $modules |
123
|
|
|
* |
124
|
|
|
* @throws MalformedFile |
125
|
|
|
* |
126
|
|
|
* @return Module[][] |
127
|
|
|
*/ |
128
|
|
|
private function findDependencyCycles($modules) |
129
|
|
|
{ |
130
|
|
|
$dependenciesGraph = $this->modulesReader->generateDependenciesGraph($modules); |
131
|
|
|
|
132
|
|
|
return $dependenciesGraph->findAllCycles(); |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.