Completed
Push — develop ( 9193e7...62056c )
by Jaap
12:45 queued 02:43
created

Graph   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 288
rs 8.6
c 1
b 0
f 0
wmc 37
lcom 1
cbo 13

9 Methods

Rating   Name   Duplication   Size   Complexity  
A transform() 0 5 1
C processClass() 0 72 12
B createEdge() 0 22 6
B createNamespaceGraph() 0 27 5
A createEmptyNode() 0 13 2
C buildNamespaceTree() 0 42 7
A getDestinationPath() 0 7 1
A checkIfGraphVizIsInstalled() 0 11 2
A createGraphForNamespace() 0 13 1
1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.3
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\Plugin\Graphs\Writer;
13
14
use phpDocumentor\Descriptor\ClassDescriptor;
15
use phpDocumentor\Descriptor\Collection;
16
use phpDocumentor\Descriptor\DescriptorAbstract;
17
use phpDocumentor\Descriptor\InterfaceDescriptor;
18
use phpDocumentor\Descriptor\NamespaceDescriptor;
19
use phpDocumentor\Descriptor\ProjectDescriptor;
20
use phpDocumentor\Descriptor\TraitDescriptor;
21
use phpDocumentor\GraphViz\Edge;
22
use phpDocumentor\GraphViz\Graph as GraphVizGraph;
23
use phpDocumentor\GraphViz\Node;
24
use phpDocumentor\Reflection\Fqsen;
25
use phpDocumentor\Transformer\Transformation;
26
use phpDocumentor\Transformer\Writer\WriterAbstract;
27
use Zend\Stdlib\Exception\ExtensionNotLoadedException;
28
29
/**
30
 * Writer responsible for generating various graphs.
31
 *
32
 * The Graph writer is capable of generating a Graph (as provided using the 'source' parameter) at the location provided
33
 * using the artifact parameter.
34
 *
35
 * Currently supported:
36
 *
37
 * * 'class', a Class Diagram Generated using GraphViz
38
 *
39
 * @todo Fix this class
40
 */
41
class Graph extends WriterAbstract
0 ignored issues
show
Complexity introduced by
The class Graph has a coupling between objects value of 16. Consider to reduce the number of dependencies under 13.
Loading history...
42
{
43
    /** @var string Name of the font to use to display the node labels with */
44
    protected $nodeFont = 'Courier';
45
46
    /** @var Node[] a cache where nodes for classes, interfaces and traits are stored for reference */
47
    protected $nodeCache = array();
48
49
    /** @var GraphVizGraph[] */
50
    protected $namespaceCache = array();
51
52
    /**
53
     * Invokes the query method contained in this class.
54
     *
55
     * @param ProjectDescriptor $project        Document containing the structure.
56
     * @param Transformation    $transformation Transformation to execute.
57
     *
58
     * @return void
59
     */
60
    public function transform(ProjectDescriptor $project, Transformation $transformation)
61
    {
62
        $type_method = 'process' . ucfirst($transformation->getSource());
63
        $this->$type_method($project, $transformation);
64
    }
65
66
    /**
67
     * Creates a class inheritance diagram.
68
     *
69
     * @param ProjectDescriptor $project
70
     * @param Transformation    $transformation
71
     *
72
     * @return void
73
     */
74
    public function processClass(ProjectDescriptor $project, Transformation $transformation)
0 ignored issues
show
Complexity introduced by
This operation has 330 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
75
    {
76
        try {
77
            $this->checkIfGraphVizIsInstalled();
78
        } catch (\Exception $e) {
79
            echo $e->getMessage();
80
81
            return;
82
        }
83
84
        if ($transformation->getParameter('font') !== null && $transformation->getParameter('font')->getValue()) {
85
            $this->nodeFont = $transformation->getParameter('font')->getValue();
86
        } else {
87
            $this->nodeFont = 'Courier';
88
        }
89
90
        $filename = $this->getDestinationPath($transformation);
91
92
        $graph = GraphVizGraph::create()
93
            ->setRankSep('1.0')
94
            ->setCenter('true')
95
            ->setRank('source')
96
            ->setRankDir('RL')
97
            ->setSplines('true')
98
            ->setConcentrate('true');
99
100
        $this->buildNamespaceTree($graph, $project->getNamespace());
101
102
        $classes    = $project->getIndexes()->get('classes', new Collection())->getAll();
103
        $interfaces = $project->getIndexes()->get('interfaces', new Collection())->getAll();
104
        $traits     = $project->getIndexes()->get('traits', new Collection())->getAll();
105
106
        /** @var ClassDescriptor[]|InterfaceDescriptor[]|TraitDescriptor[] $containers  */
107
        $containers = array_merge($classes, $interfaces, $traits);
108
109
        foreach ($containers as $container) {
110
            $from_name = (string)$container->getFullyQualifiedStructuralElementName();
111
112
            $parents     = array();
113
            $implemented = array();
114
            if ($container instanceof ClassDescriptor) {
115
                if ($container->getParent()) {
116
                    $parents[] = $container->getParent();
117
                }
118
                $implemented = $container->getInterfaces()->getAll();
119
            }
120
            if ($container instanceof InterfaceDescriptor) {
121
                $parents = $container->getParent()->getAll();
0 ignored issues
show
Documentation Bug introduced by
The method getAll does not exist on object<phpDocumentor\Descriptor\ClassDescriptor>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
122
            }
123
124
            /** @var string|ClassDescriptor|InterfaceDescriptor $parent */
125
            foreach ($parents as $parent) {
126
                $edge = $this->createEdge($graph, $from_name, $parent);
127
                if ($edge !== null) {
128
                    $edge->setArrowHead('empty');
129
                    $graph->link($edge);
130
                }
131
            }
132
133
            /** @var string|ClassDescriptor|InterfaceDescriptor $parent */
134
            foreach ($implemented as $parent) {
135
                $edge = $this->createEdge($graph, $from_name, $parent);
136
                if ($edge !== null) {
137
                    $edge->setStyle('dotted');
138
                    $edge->setArrowHead('empty');
139
                    $graph->link($edge);
140
                }
141
            }
142
        }
143
144
        $graph->export('svg', $filename);
145
    }
146
147
    /**
148
     * Creates a GraphViz Edge between two nodes.
149
     *
150
     * @param Graph  $graph
151
     * @param string $from_name
152
     * @param string|ClassDescriptor|InterfaceDescriptor|TraitDescriptor $to
153
     *
154
     * @return Edge|null
155
     */
156
    protected function createEdge($graph, $from_name, $to)
0 ignored issues
show
Unused Code introduced by
The parameter $graph is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $to. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
157
    {
158
        $to_name = (string)($to instanceof DescriptorAbstract ? $to->getFullyQualifiedStructuralElementName() : $to);
159
160
        if (!isset($this->nodeCache[$from_name])) {
161
            $namespaceParts = explode('\\', $from_name);
162
            $this->nodeCache[$from_name] = $this->createEmptyNode(array_pop($namespaceParts), $this->createNamespaceGraph($from_name));
163
        }
164
165
        if (!isset($this->nodeCache[$to_name])) {
166
            $namespaceParts = explode('\\', $to_name);
167
            $this->nodeCache[$to_name] = $this->createEmptyNode(array_pop($namespaceParts), $this->createNamespaceGraph($to_name));
168
        }
169
170
        $fromNode = $this->nodeCache[$from_name];
171
        $toNode = $this->nodeCache[$to_name];
172
        if ($fromNode !== null && $toNode !== null) {
173
            return Edge::create($fromNode, $toNode);
174
        }
175
176
        return null;
177
    }
178
179
    protected function createNamespaceGraph($fqcn)
180
    {
181
        $namespaceParts = explode('\\', $fqcn);
182
183
        // push the classname off the stack
184
        array_pop($namespaceParts);
185
186
        $graph = null;
187
        $reassembledFqnn = '';
188
        foreach ($namespaceParts as $part) {
189
            if ($part == '\\' || $part == '') {
190
                $part = 'Global';
191
                $reassembledFqnn = 'Global';
192
            } else {
193
                $reassembledFqnn = $reassembledFqnn . '\\' . $part;
194
            }
195
            if (isset($this->namespaceCache[$part])) {
196
                $graph = $this->namespaceCache[$part];
197
            } else {
198
                $subgraph = $this->createGraphForNamespace($reassembledFqnn, $part);
199
                $graph->addGraph($subgraph);
200
                $graph = $subgraph;
201
            }
202
        }
203
204
        return $graph;
205
    }
206
207
    /**
208
     * @param string $name
209
     * @param Graph $graph
210
     *
211
     * @return Node
212
     */
213
    protected function createEmptyNode($name, $graph)
214
    {
215
        if ($graph === null) {
216
            return null;
217
        }
218
219
        $node = Node::create($name);
220
        $node->setFontColor('gray');
221
        $node->setLabel($name);
222
        $graph->setNode($node);
0 ignored issues
show
Bug introduced by
The method setNode() does not seem to exist on object<phpDocumentor\Plugin\Graphs\Writer\Graph>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
223
224
        return $node;
225
    }
226
227
    /**
228
     * Builds a tree of namespace subgraphs with their classes associated.
229
     *
230
     * @param GraphVizGraph       $graph
231
     * @param NamespaceDescriptor $namespace
232
     *
233
     * @return void
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((string)$sub_element->getFullyQualifiedStructuralElementName(), $sub_element->getName())
255
                ->setShape('box')
256
                ->setFontName($this->nodeFont)
257
                ->setFontSize('11');
258
259
            if ($sub_element instanceof ClassDescriptor && $sub_element->isAbstract()) {
260
                $node->setLabel('<«abstract»<br/>' . $sub_element->getName(). '>');
261
            }
262
263
            //$full_name = $sub_element->getFullyQualifiedStructuralElementName();
264
            //$node->setURL($this->class_paths[$full_name]);
265
            //$node->setTarget('_parent');
266
267
            $this->nodeCache[(string)$sub_element->getFullyQualifiedStructuralElementName()] = $node;
268
            $sub_graph->setNode($node);
269
        }
270
271
        foreach ($namespace->getChildren()->getAll() as $element) {
272
            $this->buildNamespaceTree($sub_graph, $element);
273
        }
274
275
        $graph->addGraph($sub_graph);
276
    }
277
278
    /**
279
     * @param \phpDocumentor\Transformer\Transformation $transformation
280
     * @return string
281
     */
282
    protected function getDestinationPath(Transformation $transformation)
283
    {
284
        $filename = $transformation->getTransformer()->getTarget()
285
            . DIRECTORY_SEPARATOR . $transformation->getArtifact();
286
287
        return $filename;
288
    }
289
290
    /**
291
     * Checks whether GraphViz is installed and throws an Exception otherwise.
292
     *
293
     * @throws ExtensionNotLoadedException if graphviz is not found.
294
     *
295
     * @return void
296
     */
297
    protected function checkIfGraphVizIsInstalled()
298
    {
299
        // NOTE: the -V flag sends output using STDERR and STDOUT
300
        exec('dot -V 2>&1', $output, $error);
301
        if ($error != 0) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $error of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
302
            throw new ExtensionNotLoadedException(
303
                'Unable to find the `dot` command of the GraphViz package. '
304
                . 'Is GraphViz correctly installed and present in your path?'
305
            );
306
        }
307
    }
308
309
    /**
310
     * @param $full_namespace_name
311
     * @param $label
312
     *
313
     * @return mixed
314
     */
315
    protected function createGraphForNamespace($full_namespace_name, $label)
316
    {
317
        $sub_graph = GraphVizGraph::create('cluster_' . $full_namespace_name)
318
            ->setLabel($label)
319
            ->setStyle('rounded')
320
            ->setColor('gray')
321
            ->setFontColor('gray')
322
            ->setFontSize('11')
323
            ->setRankDir('LR')
324
        ;
325
326
        return $sub_graph;
327
    }
328
}
329