These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | /** |
||
5 | * This file is part of phpDocumentor. |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | * |
||
10 | * @author Mike van Riel <[email protected]> |
||
11 | * @copyright 2010-2018 Mike van Riel / Naenius (http://www.naenius.com) |
||
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT |
||
13 | * @link http://phpdoc.org |
||
14 | */ |
||
15 | |||
16 | namespace phpDocumentor\Transformer\Writer; |
||
17 | |||
18 | use phpDocumentor\Descriptor\ClassDescriptor; |
||
19 | use phpDocumentor\Descriptor\Collection; |
||
20 | use phpDocumentor\Descriptor\DescriptorAbstract; |
||
21 | use phpDocumentor\Descriptor\InterfaceDescriptor; |
||
22 | use phpDocumentor\Descriptor\NamespaceDescriptor; |
||
23 | use phpDocumentor\Descriptor\ProjectDescriptor; |
||
24 | use phpDocumentor\Descriptor\TraitDescriptor; |
||
25 | use phpDocumentor\GraphViz\Edge; |
||
26 | use phpDocumentor\GraphViz\Graph as GraphVizGraph; |
||
27 | use phpDocumentor\GraphViz\Node; |
||
28 | use phpDocumentor\Transformer\Transformation; |
||
29 | use phpDocumentor\Transformer\Writer\WriterAbstract; |
||
30 | use Zend\Stdlib\Exception\ExtensionNotLoadedException; |
||
31 | |||
32 | /** |
||
33 | * Writer responsible for generating various graphs. |
||
34 | * |
||
35 | * The Graph writer is capable of generating a Graph (as provided using the 'source' parameter) at the location provided |
||
36 | * using the artifact parameter. |
||
37 | * |
||
38 | * Currently supported: |
||
39 | * |
||
40 | * * 'class', a Class Diagram Generated using GraphViz |
||
41 | * |
||
42 | * @todo Fix this class |
||
43 | */ |
||
44 | class Graph extends WriterAbstract |
||
45 | { |
||
46 | /** @var string Name of the font to use to display the node labels with */ |
||
47 | protected $nodeFont = 'Courier'; |
||
48 | |||
49 | /** @var Node[] a cache where nodes for classes, interfaces and traits are stored for reference */ |
||
50 | protected $nodeCache = []; |
||
51 | |||
52 | /** @var GraphVizGraph[] */ |
||
53 | protected $namespaceCache = []; |
||
54 | |||
55 | /** |
||
56 | * Invokes the query method contained in this class. |
||
57 | * |
||
58 | * @param ProjectDescriptor $project Document containing the structure. |
||
59 | * @param Transformation $transformation Transformation to execute. |
||
60 | */ |
||
61 | public function transform(ProjectDescriptor $project, Transformation $transformation) |
||
62 | { |
||
63 | $type_method = 'process' . ucfirst($transformation->getSource()); |
||
64 | $this->{$type_method}($project, $transformation); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Creates a class inheritance diagram. |
||
69 | */ |
||
70 | public function processClass(ProjectDescriptor $project, Transformation $transformation) |
||
71 | { |
||
72 | try { |
||
73 | $this->checkIfGraphVizIsInstalled(); |
||
74 | } catch (\Exception $e) { |
||
75 | echo $e->getMessage(); |
||
76 | |||
77 | return; |
||
78 | } |
||
79 | |||
80 | if ($transformation->getParameter('font') !== null && $transformation->getParameter('font')->getValue()) { |
||
81 | $this->nodeFont = $transformation->getParameter('font')->getValue(); |
||
82 | } else { |
||
83 | $this->nodeFont = 'Courier'; |
||
84 | } |
||
85 | |||
86 | $filename = $this->getDestinationPath($transformation); |
||
87 | |||
88 | $graph = GraphVizGraph::create() |
||
89 | ->setRankSep('1.0') |
||
90 | ->setCenter('true') |
||
91 | ->setRank('source') |
||
92 | ->setRankDir('RL') |
||
93 | ->setSplines('true') |
||
94 | ->setConcentrate('true'); |
||
95 | |||
96 | $this->buildNamespaceTree($graph, $project->getNamespace()); |
||
97 | |||
98 | $classes = $project->getIndexes()->get('classes', new Collection())->getAll(); |
||
99 | $interfaces = $project->getIndexes()->get('interfaces', new Collection())->getAll(); |
||
100 | $traits = $project->getIndexes()->get('traits', new Collection())->getAll(); |
||
101 | |||
102 | /** @var ClassDescriptor[]|InterfaceDescriptor[]|TraitDescriptor[] $containers */ |
||
103 | $containers = array_merge($classes, $interfaces, $traits); |
||
104 | |||
105 | foreach ($containers as $container) { |
||
106 | $from_name = (string) $container->getFullyQualifiedStructuralElementName(); |
||
107 | |||
108 | $parents = []; |
||
109 | $implemented = []; |
||
110 | if ($container instanceof ClassDescriptor) { |
||
111 | if ($container->getParent()) { |
||
112 | $parents[] = $container->getParent(); |
||
113 | } |
||
114 | |||
115 | $implemented = $container->getInterfaces()->getAll(); |
||
116 | } |
||
117 | |||
118 | if ($container instanceof InterfaceDescriptor) { |
||
119 | $parents = $container->getParent()->getAll(); |
||
120 | } |
||
121 | |||
122 | /** @var string|ClassDescriptor|InterfaceDescriptor $parent */ |
||
123 | foreach ($parents as $parent) { |
||
124 | $edge = $this->createEdge($graph, $from_name, $parent); |
||
125 | if ($edge !== null) { |
||
126 | $edge->setArrowHead('empty'); |
||
127 | $graph->link($edge); |
||
128 | } |
||
129 | } |
||
130 | |||
131 | /** @var string|ClassDescriptor|InterfaceDescriptor $parent */ |
||
132 | foreach ($implemented as $parent) { |
||
133 | $edge = $this->createEdge($graph, $from_name, $parent); |
||
134 | if ($edge !== null) { |
||
135 | $edge->setStyle('dotted'); |
||
136 | $edge->setArrowHead('empty'); |
||
137 | $graph->link($edge); |
||
138 | } |
||
139 | } |
||
140 | } |
||
141 | |||
142 | $graph->export('svg', $filename); |
||
143 | } |
||
144 | |||
145 | /** |
||
146 | * Creates a GraphViz Edge between two nodes. |
||
147 | * |
||
148 | * @param Graph $graph |
||
149 | * @param string $from_name |
||
150 | * @param string|ClassDescriptor|InterfaceDescriptor|TraitDescriptor $to |
||
151 | * |
||
152 | * @return Edge|null |
||
153 | */ |
||
154 | protected function createEdge($graph, $from_name, $to) |
||
155 | { |
||
156 | $to_name = (string) ($to instanceof DescriptorAbstract ? $to->getFullyQualifiedStructuralElementName() : $to); |
||
157 | |||
158 | if (!isset($this->nodeCache[$from_name])) { |
||
159 | $namespaceParts = explode('\\', $from_name); |
||
160 | $this->nodeCache[$from_name] = $this->createEmptyNode( |
||
161 | array_pop($namespaceParts), |
||
162 | $this->createNamespaceGraph($from_name) |
||
163 | ); |
||
164 | } |
||
165 | |||
166 | if (!isset($this->nodeCache[$to_name])) { |
||
167 | $namespaceParts = explode('\\', $to_name); |
||
168 | $this->nodeCache[$to_name] = $this->createEmptyNode( |
||
169 | array_pop($namespaceParts), |
||
170 | $this->createNamespaceGraph($to_name) |
||
171 | ); |
||
172 | } |
||
173 | |||
174 | $fromNode = $this->nodeCache[$from_name]; |
||
175 | $toNode = $this->nodeCache[$to_name]; |
||
176 | if ($fromNode !== null && $toNode !== null) { |
||
177 | return Edge::create($fromNode, $toNode); |
||
178 | } |
||
179 | |||
180 | return null; |
||
181 | } |
||
182 | |||
183 | protected function createNamespaceGraph($fqcn) |
||
184 | { |
||
185 | $namespaceParts = explode('\\', $fqcn); |
||
186 | |||
187 | // push the classname off the stack |
||
188 | array_pop($namespaceParts); |
||
189 | |||
190 | $graph = null; |
||
191 | $reassembledFqnn = ''; |
||
192 | foreach ($namespaceParts as $part) { |
||
193 | if ($part === '\\' || $part === '') { |
||
194 | $part = 'Global'; |
||
195 | $reassembledFqnn = 'Global'; |
||
196 | } else { |
||
197 | $reassembledFqnn = $reassembledFqnn . '\\' . $part; |
||
198 | } |
||
199 | |||
200 | if (isset($this->namespaceCache[$part])) { |
||
201 | $graph = $this->namespaceCache[$part]; |
||
202 | } else { |
||
203 | $subgraph = $this->createGraphForNamespace($reassembledFqnn, $part); |
||
204 | $graph->addGraph($subgraph); |
||
205 | $graph = $subgraph; |
||
206 | } |
||
207 | } |
||
208 | |||
209 | return $graph; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * @param string $name |
||
214 | * @param Graph $graph |
||
215 | * |
||
216 | * @return Node |
||
217 | */ |
||
218 | protected function createEmptyNode($name, $graph) |
||
219 | { |
||
220 | if ($graph === null) { |
||
221 | return null; |
||
222 | } |
||
223 | |||
224 | $node = Node::create($name); |
||
225 | $node->setFontColor('gray'); |
||
226 | $node->setLabel($name); |
||
227 | $graph->setNode($node); |
||
228 | |||
229 | return $node; |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Builds a tree of namespace subgraphs with their classes associated. |
||
234 | */ |
||
235 | protected function buildNamespaceTree(GraphVizGraph $graph, NamespaceDescriptor $namespace) |
||
236 | { |
||
237 | $full_namespace_name = (string) $namespace->getFullyQualifiedStructuralElementName(); |
||
238 | if ($full_namespace_name === '\\') { |
||
239 | $full_namespace_name = 'Global'; |
||
240 | } |
||
241 | |||
242 | $label = $namespace->getName() === '\\' ? 'Global' : $namespace->getName(); |
||
243 | $sub_graph = $this->createGraphForNamespace($full_namespace_name, $label); |
||
244 | $this->namespaceCache[$full_namespace_name] = $sub_graph; |
||
245 | |||
246 | $elements = array_merge( |
||
247 | $namespace->getClasses()->getAll(), |
||
248 | $namespace->getInterfaces()->getAll(), |
||
249 | $namespace->getTraits()->getAll() |
||
250 | ); |
||
251 | |||
252 | /** @var ClassDescriptor|InterfaceDescriptor|TraitDescriptor $sub_element */ |
||
253 | foreach ($elements as $sub_element) { |
||
254 | $node = Node::create( |
||
255 | (string) $sub_element->getFullyQualifiedStructuralElementName(), |
||
256 | $sub_element->getName() |
||
257 | ) |
||
258 | ->setShape('box') |
||
259 | ->setFontName($this->nodeFont) |
||
260 | ->setFontSize('11'); |
||
261 | |||
262 | if ($sub_element instanceof ClassDescriptor && $sub_element->isAbstract()) { |
||
263 | $node->setLabel('<«abstract»<br/>' . $sub_element->getName() . '>'); |
||
264 | } |
||
265 | |||
266 | //$full_name = $sub_element->getFullyQualifiedStructuralElementName(); |
||
0 ignored issues
–
show
|
|||
267 | //$node->setURL($this->class_paths[$full_name]); |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
84% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them.
Loading history...
|
|||
268 | //$node->setTarget('_parent'); |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
86% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them.
Loading history...
|
|||
269 | |||
270 | $this->nodeCache[(string) $sub_element->getFullyQualifiedStructuralElementName()] = $node; |
||
271 | $sub_graph->setNode($node); |
||
272 | } |
||
273 | |||
274 | foreach ($namespace->getChildren()->getAll() as $element) { |
||
275 | $this->buildNamespaceTree($sub_graph, $element); |
||
276 | } |
||
277 | |||
278 | $graph->addGraph($sub_graph); |
||
279 | } |
||
280 | |||
281 | protected function getDestinationPath(Transformation $transformation) |
||
282 | { |
||
283 | return $transformation->getTransformer()->getTarget() |
||
284 | . DIRECTORY_SEPARATOR . $transformation->getArtifact(); |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Checks whether GraphViz is installed and throws an Exception otherwise. |
||
289 | * |
||
290 | * @throws ExtensionNotLoadedException if graphviz is not found. |
||
291 | */ |
||
292 | protected function checkIfGraphVizIsInstalled() |
||
293 | { |
||
294 | // NOTE: the -V flag sends output using STDERR and STDOUT |
||
295 | exec('dot -V 2>&1', $output, $error); |
||
296 | if ($error !== 0) { |
||
297 | throw new ExtensionNotLoadedException( |
||
298 | 'Unable to find the `dot` command of the GraphViz package. ' |
||
299 | . 'Is GraphViz correctly installed and present in your path?' |
||
300 | ); |
||
301 | } |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * @param string $full_namespace_name |
||
306 | * @param string $label |
||
307 | * |
||
308 | * @return mixed |
||
309 | */ |
||
310 | protected function createGraphForNamespace($full_namespace_name, $label) |
||
311 | { |
||
312 | return GraphVizGraph::create('cluster_' . $full_namespace_name) |
||
313 | ->setLabel($label) |
||
314 | ->setStyle('rounded') |
||
315 | ->setColor('gray') |
||
316 | ->setFontColor('gray') |
||
317 | ->setFontSize('11') |
||
318 | ->setRankDir('LR'); |
||
319 | } |
||
320 | } |
||
321 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.