1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* phpDocumentor |
4
|
|
|
* |
5
|
|
|
* PHP Version 5.4 |
6
|
|
|
* |
7
|
|
|
* @copyright 2010-2014 Mike van Riel / Naenius (http://www.naenius.com) |
8
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT |
9
|
|
|
* @link http://phpdoc.org |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace phpDocumentor\Application\Renderer\Template\Action; |
13
|
|
|
|
14
|
|
|
use phpDocumentor\GraphViz\Edge; |
15
|
|
|
use phpDocumentor\GraphViz\Graph as GraphVizGraph; |
16
|
|
|
use phpDocumentor\GraphViz\Node; |
17
|
|
|
use phpDocumentor\DomainModel\Renderer\Template\Action; |
18
|
|
|
use phpDocumentor\DomainModel\Renderer\Template\ActionHandler; |
19
|
|
|
|
20
|
|
|
class GraphHandler implements ActionHandler |
|
|
|
|
21
|
|
|
{ |
22
|
|
|
/** @var string Name of the font to use to display the node labels with */ |
23
|
|
|
protected $nodeFont = 'Courier'; |
24
|
|
|
|
25
|
|
|
/** @var Node[] a cache where nodes for classes, interfaces and traits are stored for reference */ |
26
|
|
|
protected $nodeCache = array(); |
27
|
|
|
|
28
|
|
|
/** @var Analyzer */ |
29
|
|
|
private $analyzer; |
30
|
|
|
|
31
|
|
|
public function __construct(Analyzer $analyzer) |
32
|
|
|
{ |
33
|
|
|
$this->analyzer = $analyzer; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Executes the activities that this Action represents. |
38
|
|
|
* |
39
|
|
|
* @param Action|Graph $action |
40
|
|
|
* |
41
|
|
|
* @return void |
42
|
|
|
*/ |
43
|
|
|
public function __invoke(Action $action) |
44
|
|
|
{ |
45
|
|
|
$project = $this->analyzer->getProjectDescriptor(); |
46
|
|
|
try { |
47
|
|
|
$this->checkIfGraphVizIsInstalled(); |
48
|
|
|
} catch (\Exception $e) { |
49
|
|
|
echo $e->getMessage(); |
50
|
|
|
|
51
|
|
|
return; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
$graph = GraphVizGraph::create() |
55
|
|
|
->setRankSep('1.0') |
56
|
|
|
->setCenter('true') |
57
|
|
|
->setRank('source') |
58
|
|
|
->setRankDir('RL') |
59
|
|
|
->setSplines('true') |
60
|
|
|
->setConcentrate('true'); |
61
|
|
|
|
62
|
|
|
$this->buildNamespaceTree($graph, $project->getNamespace()); |
63
|
|
|
|
64
|
|
|
$classes = $project->getIndexes()->get('classes', new Collection())->getAll(); |
65
|
|
|
$interfaces = $project->getIndexes()->get('interfaces', new Collection())->getAll(); |
66
|
|
|
$traits = $project->getIndexes()->get('traits', new Collection())->getAll(); |
67
|
|
|
|
68
|
|
|
/** @var ClassDescriptor[]|InterfaceDescriptor[]|TraitDescriptor[] $containers */ |
69
|
|
|
$containers = array_merge($classes, $interfaces, $traits); |
70
|
|
|
|
71
|
|
|
foreach ($containers as $container) { |
72
|
|
|
$from_name = $container->getFullyQualifiedStructuralElementName(); |
73
|
|
|
|
74
|
|
|
$parents = array(); |
75
|
|
|
$implemented = array(); |
76
|
|
|
if ($container instanceof ClassDescriptor) { |
|
|
|
|
77
|
|
|
if ($container->getParent()) { |
78
|
|
|
$parents[] = $container->getParent(); |
79
|
|
|
} |
80
|
|
|
$implemented = $container->getInterfaces()->getAll(); |
81
|
|
|
} |
82
|
|
|
if ($container instanceof InterfaceDescriptor) { |
|
|
|
|
83
|
|
|
$parents = $container->getParent()->getAll(); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** @var string|ClassDescriptor|InterfaceDescriptor $parent */ |
87
|
|
|
foreach ($parents as $parent) { |
88
|
|
|
$edge = $this->createEdge($graph, $from_name, $parent); |
89
|
|
|
$edge->setArrowHead('empty'); |
90
|
|
|
$graph->link($edge); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** @var string|ClassDescriptor|InterfaceDescriptor $parent */ |
94
|
|
|
foreach ($implemented as $parent) { |
95
|
|
|
$edge = $this->createEdge($graph, $from_name, $parent); |
96
|
|
|
$edge->setStyle('dotted'); |
97
|
|
|
$edge->setArrowHead('empty'); |
98
|
|
|
$graph->link($edge); |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$destination = $action->getRenderContext()->getDestination() . '/' . $action->getDestination(); |
|
|
|
|
103
|
|
|
if (! file_exists(dirname($destination))) { |
104
|
|
|
@mkdir(dirname($destination), 0777, true); |
|
|
|
|
105
|
|
|
} |
106
|
|
|
$graph->export('svg', $destination); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Creates a GraphViz Edge between two nodes. |
111
|
|
|
* |
112
|
|
|
* @param Graph $graph |
113
|
|
|
* @param string $from_name |
114
|
|
|
* @param string|ClassDescriptor|InterfaceDescriptor|TraitDescriptor $to |
115
|
|
|
* |
116
|
|
|
* @return Edge |
117
|
|
|
*/ |
118
|
|
|
protected function createEdge($graph, $from_name, $to) |
|
|
|
|
119
|
|
|
{ |
120
|
|
|
$to_name = !is_string($to) ? $to->getFullyQualifiedStructuralElementName() : $to; |
121
|
|
|
|
122
|
|
|
if (!isset($this->nodeCache[$from_name])) { |
123
|
|
|
$this->nodeCache[$from_name] = $this->createEmptyNode($from_name, $graph); |
124
|
|
|
} |
125
|
|
|
if (!isset($this->nodeCache[$to_name])) { |
126
|
|
|
$this->nodeCache[$to_name] = $this->createEmptyNode($to_name, $graph); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return Edge::create($this->nodeCache[$from_name], $this->nodeCache[$to_name]); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param string $name |
134
|
|
|
* @param Graph $graph |
135
|
|
|
* |
136
|
|
|
* @return Node |
137
|
|
|
*/ |
138
|
|
|
protected function createEmptyNode($name, $graph) |
139
|
|
|
{ |
140
|
|
|
$node = Node::create($name); |
141
|
|
|
$node->setFontColor('gray'); |
142
|
|
|
$node->setLabel($name); |
143
|
|
|
$graph->setNode($node); |
|
|
|
|
144
|
|
|
|
145
|
|
|
return $node; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Builds a tree of namespace subgraphs with their classes associated. |
150
|
|
|
* |
151
|
|
|
* @param GraphVizGraph $graph |
152
|
|
|
* @param NamespaceDescriptor $namespace |
153
|
|
|
* |
154
|
|
|
* @return void |
155
|
|
|
*/ |
156
|
|
|
protected function buildNamespaceTree(GraphVizGraph $graph, NamespaceDescriptor $namespace) |
157
|
|
|
{ |
158
|
|
|
$full_namespace_name = $namespace->getFullyQualifiedStructuralElementName(); |
159
|
|
|
if ($full_namespace_name == '\\') { |
160
|
|
|
$full_namespace_name = 'Global'; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$sub_graph = GraphVizGraph::create('cluster_' . $full_namespace_name) |
164
|
|
|
->setLabel($namespace->getName() == '\\' ? 'Global' : $namespace->getName()) |
165
|
|
|
->setStyle('rounded') |
166
|
|
|
->setColor('gray') |
167
|
|
|
->setFontColor('gray') |
168
|
|
|
->setFontSize('11') |
169
|
|
|
->setRankDir('LR'); |
170
|
|
|
|
171
|
|
|
$elements = array_merge( |
172
|
|
|
$namespace->getClasses()->getAll(), |
173
|
|
|
$namespace->getInterfaces()->getAll(), |
174
|
|
|
$namespace->getTraits()->getAll() |
175
|
|
|
); |
176
|
|
|
|
177
|
|
|
/** @var ClassDescriptor|InterfaceDescriptor|TraitDescriptor $sub_element */ |
178
|
|
|
foreach ($elements as $sub_element) { |
179
|
|
|
$node = Node::create($sub_element->getFullyQualifiedStructuralElementName(), $sub_element->getName()) |
180
|
|
|
->setShape('box') |
181
|
|
|
->setFontName($this->nodeFont) |
182
|
|
|
->setFontSize('11'); |
183
|
|
|
|
184
|
|
|
if ($sub_element instanceof ClassDescriptor && $sub_element->isAbstract()) { |
|
|
|
|
185
|
|
|
$node->setLabel('<«abstract»<br/>' . $sub_element->getName(). '>'); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
//$full_name = $sub_element->getFullyQualifiedStructuralElementName(); |
189
|
|
|
//$node->setURL($this->class_paths[$full_name]); |
190
|
|
|
//$node->setTarget('_parent'); |
191
|
|
|
|
192
|
|
|
$this->nodeCache[$sub_element->getFullyQualifiedStructuralElementName()] = $node; |
193
|
|
|
$sub_graph->setNode($node); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
foreach ($namespace->getChildren()->getAll() as $element) { |
197
|
|
|
$this->buildNamespaceTree($sub_graph, $element); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$graph->addGraph($sub_graph); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Checks whether GraphViz is installed and throws an Exception otherwise. |
205
|
|
|
* |
206
|
|
|
* @throws \RuntimeException if graphviz is not found. |
207
|
|
|
* |
208
|
|
|
* @return void |
209
|
|
|
*/ |
210
|
|
|
protected function checkIfGraphVizIsInstalled() |
211
|
|
|
{ |
212
|
|
|
// NOTE: the -V flag sends output using STDERR and STDOUT |
213
|
|
|
exec('dot -V 2>&1', $output, $error); |
214
|
|
|
if ($error != 0) { |
|
|
|
|
215
|
|
|
throw new \RuntimeException( |
216
|
|
|
'Unable to find the `dot` command of the GraphViz package. ' |
217
|
|
|
. 'Is GraphViz correctly installed and present in your path?' |
218
|
|
|
); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|