1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Drupal\qa; |
4
|
|
|
|
5
|
|
|
use Doctrine\Common\Util\Debug; |
6
|
|
|
use Drupal\Core\Extension\ModuleExtensionList; |
7
|
|
|
use Drupal\Core\Extension\ThemeHandlerInterface; |
8
|
|
|
use Grafizzi\Graph\Attribute; |
9
|
|
|
use Grafizzi\Graph\Cluster; |
10
|
|
|
use Grafizzi\Graph\Edge; |
11
|
|
|
use Grafizzi\Graph\Graph; |
12
|
|
|
use Grafizzi\Graph\Node; |
13
|
|
|
use Pimple\Container; |
14
|
|
|
use Psr\Log\LoggerInterface; |
15
|
|
|
|
16
|
|
|
class Dependencies { |
17
|
|
|
const SHAPE_THEME = 'octagon'; |
18
|
|
|
const SHAPE_ENGINE = 'doubleoctagon'; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var \Grafizzi\Graph\Attribute |
22
|
|
|
*/ |
23
|
|
|
protected $font; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var \Psr\Log\LoggerInterface |
27
|
|
|
*/ |
28
|
|
|
protected $logger; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \Drupal\Core\Extension\ModuleExtensionList |
32
|
|
|
*/ |
33
|
|
|
protected $moduleExtensionList; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var \Pimple\Container |
37
|
|
|
*/ |
38
|
|
|
protected $pimple; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var \Drupal\Core\Extension\ThemeHandlerInterface |
42
|
|
|
*/ |
43
|
|
|
protected $themeHandler; |
44
|
|
|
|
45
|
|
|
public function __construct( |
46
|
|
|
ThemeHandlerInterface $themeHandler, |
47
|
|
|
ModuleExtensionList $moduleExtensionList, |
48
|
|
|
LoggerInterface $logger |
49
|
|
|
) { |
50
|
|
|
$this->logger = $logger; |
51
|
|
|
$this->moduleExtensionList = $moduleExtensionList; |
52
|
|
|
$this->pimple = new Container(['logger' => $logger]); |
53
|
|
|
$this->themeHandler = $themeHandler; |
54
|
|
|
|
55
|
|
|
$this->font = $this->attr("fontsize", 10); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Clone of function _graphviz_create_filepath() from graphviz_filter.module. |
61
|
|
|
* |
62
|
|
|
* @param string $path |
63
|
|
|
* @param string $filename |
64
|
|
|
* |
65
|
|
|
* @return string |
66
|
|
|
*/ |
67
|
|
|
public function graphvizCreateFilepath($path, $filename) { |
68
|
|
|
if (!empty($path)) { |
69
|
|
|
return rtrim($path, '/') .'/'. $filename; |
70
|
|
|
} |
71
|
|
|
return $filename; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Facade for Grafizzi Attribute constructor. |
76
|
|
|
* |
77
|
|
|
* @param $name |
78
|
|
|
* @param $value |
79
|
|
|
* |
80
|
|
|
* @return \Grafizzi\Graph\Attribute |
81
|
|
|
*/ |
82
|
|
|
public function attr(string $name, string $value) : Attribute { |
83
|
|
|
return new Attribute($this->pimple, $name, $value); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
public function edge(Node $from, Node $to, array $attrs) : Edge { |
87
|
|
|
return new Edge($this->pimple, $from, $to, $attrs); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Facade for Grafizzi Node constructor. |
92
|
|
|
* |
93
|
|
|
* Strips the optional "namespace" (aka project or package) part of the name. |
94
|
|
|
* |
95
|
|
|
* @param string $name |
96
|
|
|
* @param array $attrs |
97
|
|
|
* |
98
|
|
|
* @return \Grafizzi\Graph\Node |
99
|
|
|
*/ |
100
|
|
|
public function node(string $name, array $attrs = []) : Node { |
101
|
|
|
// Strip the "namespace" part. |
102
|
|
|
$arName = explode(':', $name); |
103
|
|
|
$localName = array_pop($arName); |
104
|
|
|
|
105
|
|
|
$arLocal = explode(' ', $localName); |
106
|
|
|
$simpleName = current($arLocal); |
107
|
|
|
return new Node($this->pimple, $simpleName, $attrs); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Facade for Grafizzi Cluster constructor. |
112
|
|
|
* |
113
|
|
|
* @param string $name |
114
|
|
|
* |
115
|
|
|
* @return \Grafizzi\Graph\Cluster |
116
|
|
|
*/ |
117
|
|
|
public function cluster(string $name): Cluster { |
118
|
|
|
return new Cluster($this->pimple, urlencode($name), [ |
119
|
|
|
$this->attr('label', $name), |
120
|
|
|
]); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
protected function initGraph() : Graph { |
124
|
|
|
$g = new Graph($this->pimple, "deps", [ |
125
|
|
|
$this->attr("rankdir", "RL"), |
126
|
|
|
]); |
127
|
|
|
$g->setDirected(TRUE); |
128
|
|
|
return $g; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
public function buildModules(Graph $g) : Graph { |
132
|
|
|
$modules = $this->moduleExtensionList->reset()->getList(); |
133
|
|
|
krsort($modules); |
134
|
|
|
|
135
|
|
|
$packages = []; |
136
|
|
|
|
137
|
|
|
foreach ($modules as $module => $detail) { |
138
|
|
|
if (!$detail->status) { |
139
|
|
|
continue; |
140
|
|
|
} |
141
|
|
|
$package = $detail->info['package'] ?? ''; |
142
|
|
|
if (!empty($package)) { |
143
|
|
|
if (!isset($packages[$package])) { |
144
|
|
|
$packageCluster = $this->cluster($package); |
145
|
|
|
$packages[$package] = $packageCluster; |
146
|
|
|
$g->addChild($packageCluster); |
147
|
|
|
} |
148
|
|
|
else { |
149
|
|
|
/** @var \Grafizzi\Graph\Cluster $packageCluster */ |
150
|
|
|
$packageCluster = $packages[$package]; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$packageCluster->addChild($from = $this->node("${package}:${module}", [$this->font])); |
154
|
|
|
} |
155
|
|
|
else { |
156
|
|
|
$g->addChild($from = $this->node($module, [$this->font])); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$dependencies = $detail->info['dependencies'] ?? []; |
160
|
|
|
foreach ($dependencies as $depend) { |
161
|
|
|
$to = $this->node($depend, [$this->font]); |
162
|
|
|
$g->addChild( |
163
|
|
|
$this->edge($from, $to, [ |
164
|
|
|
$this->font, |
165
|
|
|
$this->attr('color', 'lightgray'), |
166
|
|
|
])); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
return $g; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
protected function buildTheming(Graph $g) : Graph { |
173
|
|
|
$engineShape = $this->attr('shape', static::SHAPE_ENGINE); |
174
|
|
|
$themeShape = $this->attr('shape', static::SHAPE_THEME); |
175
|
|
|
$engineLine = $this->attr('style', 'dotted'); |
176
|
|
|
$baseLine = $this->attr('style', 'dashed'); |
177
|
|
|
|
178
|
|
|
$themeList = $this->themeHandler->listInfo(); |
179
|
|
|
krsort($themeList); |
180
|
|
|
|
181
|
|
|
$engines = []; |
182
|
|
|
$themes = []; |
183
|
|
|
|
184
|
|
|
foreach ($themeList as $theme => $detail) { |
185
|
|
|
if (empty($detail->status)) { |
186
|
|
|
continue; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
// Build theme engine links. |
190
|
|
|
$themes[$theme] = $from = $this->node($theme, [$this->font, $themeShape]); |
191
|
|
|
$g->addChild($from); |
192
|
|
|
if (!empty($detail->owner)) { |
193
|
|
|
// D8 still theoretically supports multiple engines (e.g. nyan_cat). |
194
|
|
|
$engine = basename($detail->owner); // with extension |
195
|
|
|
$engineBase = basename($engine, '.engine'); |
196
|
|
|
if (!isset($engines[$engineBase])) { |
197
|
|
|
$engines[$engineBase] = $engineNode = $this->node($engineBase, [ |
198
|
|
|
$engineShape, |
199
|
|
|
]); |
200
|
|
|
$g->addChild($engineCluster = $this->cluster($engineBase, [$this->font])); |
|
|
|
|
201
|
|
|
$engineCluster->addChild($engineNode); |
202
|
|
|
} |
203
|
|
|
$to = $engines[$engineBase]; |
204
|
|
|
$g->addChild($this->edge($from, $to, [$this->font, $engineLine])); |
205
|
|
|
} |
206
|
|
|
else { |
207
|
|
|
$g->addChild($from); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// Build base theme links. |
211
|
|
|
$toName = $detail->base_theme ?? ''; |
212
|
|
|
if (!empty($toName)) { |
213
|
|
|
$to = $themes[$toName]; |
214
|
|
|
if (empty($to)) { |
215
|
|
|
$to = $this->node($toName, [$this->font]); |
216
|
|
|
$g->addChild($to); |
217
|
|
|
} |
218
|
|
|
$g->addChild($this->edge($from, $to, [$this->font, $baseLine])); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return $g; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
public function build() : Graph { |
226
|
|
|
// @see https://wiki.php.net/rfc/pipe-operator |
227
|
|
|
$g = $this->initGraph(); |
228
|
|
|
$g = $this->buildModules($g); |
229
|
|
|
//$g = $this->buildTheming($g); |
230
|
|
|
return $g; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
} |
234
|
|
|
|
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.