Completed
Push — master ( 6e20ec...259613 )
by Chuck
12s
created

Graph::__toString()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.9713
cc 3
eloc 15
nc 4
nop 0
crap 3
1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5
6
 *
7
 * @author    Mike van Riel <[email protected]>
8
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\GraphViz;
14
15
/**
16
 * Class representing a graph; this may be a main graph but also a subgraph.
17
 *
18
 * In case of a subgraph:
19
 * When the name of the subgraph is prefixed with _cluster_ then the contents
20
 * of this graph will be grouped and a border will be added. Otherwise it is
21
 * used as logical container to place defaults in.
22
 *
23
 * @author    Mike van Riel <[email protected]>
24
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
25
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
26
 * @link      http://phpdoc.org
27
 *
28
 * @method Graph setRankSep(string $rankSep)
29
 * @method Graph setCenter(string $center)
30
 * @method Graph setRank(string $rank)
31
 * @method Graph setRankDir(string $rankDir)
32
 * @method Graph setSplines(string $splines)
33
 * @method Graph setConcentrate(string $concentrate)
34
 */
35
class Graph
36
{
37
    /** @var string Name of this graph */
38
    protected $name = 'G';
39
40
    /** @var string Type of this graph; may be digraph, graph or subgraph */
41
    protected $type = 'digraph';
42
43
    /** @var bool If the graph is strict then multiple edges are not allowed between the same pairs of nodes */
44
    protected $strict = false;
45
46
    /** @var \phpDocumentor\GraphViz\Attribute[] A list of attributes for this Graph */
47
    protected $attributes = [];
48
49
    /** @var \phpDocumentor\GraphViz\Graph[] A list of subgraphs for this Graph */
50
    protected $graphs = [];
51
52
    /** @var \phpDocumentor\GraphViz\Node[] A list of nodes for this Graph */
53
    protected $nodes = [];
54
55
    /** @var \phpDocumentor\GraphViz\Edge[] A list of edges / arrows for this Graph */
56
    protected $edges = [];
57
58
    /** @var string The path to execute dot from */
59
    protected $path = '';
60
61
    /**
62
     * Factory method to instantiate a Graph so that you can use fluent coding
63
     * to chain everything.
64
     *
65
     * @param string $name        The name for this graph.
66
     * @param bool   $directional Whether this is a directed or undirected graph.
67
     *
68
     * @return \phpDocumentor\GraphViz\Graph
69
     */
70 1
    public static function create($name = 'G', $directional = true)
71
    {
72 1
        $graph = new self();
73
        $graph
74 1
            ->setName($name)
75 1
            ->setType($directional ? 'digraph' : 'graph');
76
77 1
        return $graph;
78
    }
79
80
    /**
81
     * Sets the path for the execution. Only needed if it is not in the PATH env.
82
     *
83
     * @param string $path The path to execute dot from
84
     *
85
     * @return \phpDocumentor\GraphViz\Graph
86
     */
87 1
    public function setPath($path)
88
    {
89 1
        $realpath = realpath($path);
90 1
        if ($path && $path === $realpath) {
91 1
            $this->path = $path . DIRECTORY_SEPARATOR;
92
        }
93
94 1
        return $this;
95
    }
96
97
    /**
98
     * Sets the name for this graph.
99
     *
100
     * If this is a subgraph you can prefix the name with _cluster_ to group all
101
     * contained nodes and add a border.
102
     *
103
     * @param string $name The new name for this graph.
104
     *
105
     * @return \phpDocumentor\GraphViz\Graph
106
     */
107 1
    public function setName($name)
108
    {
109 1
        $this->name = $name;
110 1
        return $this;
111
    }
112
113
    /**
114
     * Returns the name for this Graph.
115
     *
116
     * @return string
117
     */
118 1
    public function getName()
119
    {
120 1
        return $this->name;
121
    }
122
123
    /**
124
     * Sets the type for this graph.
125
     *
126
     * @param string $type Must be either "digraph", "graph" or "subgraph".
127
     *
128
     * @throws \InvalidArgumentException if $type is not "digraph", "graph" or
129
     *  "subgraph".
130
     *
131
     * @return \phpDocumentor\GraphViz\Graph
132
     */
133 2
    public function setType($type)
134
    {
135 2
        if (!in_array($type, ['digraph', 'graph', 'subgraph'])) {
136 1
            throw new \InvalidArgumentException(
137
                'The type for a graph must be either "digraph", "graph" or '
138 1
                . '"subgraph"'
139
            );
140
        }
141
142 1
        $this->type = $type;
143 1
        return $this;
144
    }
145
146
    /**
147
     * Returns the type of this Graph.
148
     *
149
     * @return string
150
     */
151 1
    public function getType()
152
    {
153 1
        return $this->type;
154
    }
155
156
    /**
157
     * Set if the Graph should be strict. If the graph is strict then
158
     * multiple edges are not allowed between the same pairs of nodes
159
     *
160
     * @param bool $isStrict
161
     *
162
     * @return \phpDocumentor\GraphViz\Graph
163
     */
164 2
    public function setStrict($isStrict)
165
    {
166 2
        $this->strict = $isStrict;
167 2
        return $this;
168
    }
169
170
    /**
171
     * @return bool
172
     */
173 1
    public function isStrict()
174
    {
175 1
        return $this->strict;
176
    }
177
178
    /**
179
     * Magic method to provide a getter/setter to add attributes on the Graph.
180
     *
181
     * Using this method we make sure that we support any attribute without
182
     * too much hassle. If the name for this method does not start with get
183
     * or set we return null.
184
     *
185
     * Set methods return this graph (fluent interface) whilst get methods
186
     * return the attribute value.
187
     *
188
     * @param string  $name      Name of the method including get/set
189
     * @param mixed[] $arguments The arguments, should be 1: the value
190
     *
191
     * @return \phpDocumentor\GraphViz\Attribute|\phpDocumentor\GraphViz\Graph|null
192
     */
193 1
    public function __call($name, $arguments)
194
    {
195 1
        $key = strtolower(substr($name, 3));
196 1
        if (strtolower(substr($name, 0, 3)) === 'set') {
197 1
            $this->attributes[$key] = new Attribute($key, $arguments[0]);
198
199 1
            return $this;
200
        }
201
202 1
        if (strtolower(substr($name, 0, 3)) === 'get') {
203 1
            return $this->attributes[$key];
204
        }
205
206 1
        return null;
207
    }
208
209
    /**
210
     * Adds a subgraph to this graph; automatically changes the type to subgraph.
211
     *
212
     * Please note that an index is maintained using the name of the subgraph.
213
     * Thus if you have 2 subgraphs with the same name that the first will be
214
     * overwritten by the latter.
215
     *
216
     * @param \phpDocumentor\GraphViz\Graph $graph The graph to add onto this graph as
217
     *  subgraph.
218
     *
219
     * @see \phpDocumentor\GraphViz\Graph::create()
220
     *
221
     * @return \phpDocumentor\GraphViz\Graph
222
     */
223
    public function addGraph(\phpDocumentor\GraphViz\Graph $graph)
224
    {
225
        $graph->setType('subgraph');
226
        $this->graphs[$graph->getName()] = $graph;
227
        return $this;
228
    }
229
230
    /**
231
     * Checks whether a graph with a certain name already exists.
232
     *
233
     * @param string $name Name of the graph to find.
234
     *
235
     * @return bool
236
     */
237
    public function hasGraph($name)
238
    {
239
        return isset($this->graphs[$name]);
240
    }
241
242
    /**
243
     * Returns the subgraph with a given name.
244
     *
245
     * @param string $name Name of the requested graph.
246
     *
247
     * @return \phpDocumentor\GraphViz\Graph
248
     */
249
    public function getGraph($name)
250
    {
251
        return $this->graphs[$name];
252
    }
253
254
    /**
255
     * Sets a node in the $nodes array; uses the name of the node as index.
256
     *
257
     * Nodes can be retrieved by retrieving the property with the same name.
258
     * Thus 'node1' can be retrieved by invoking: $graph->node1
259
     *
260
     * @param \phpDocumentor\GraphViz\Node $node The node to set onto this Graph.
261
     *
262
     * @see \phpDocumentor\GraphViz\Node::create()
263
     *
264
     * @return \phpDocumentor\GraphViz\Graph
265
     */
266
    public function setNode(Node $node)
267
    {
268
        $this->nodes[$node->getName()] = $node;
269
        return $this;
270
    }
271
272
    /**
273
     * Finds a node in this graph or any of its subgraphs.
274
     *
275
     * @param string $name Name of the node to find.
276
     *
277
     * @return \phpDocumentor\GraphViz\Node|null
278
     */
279
    public function findNode($name)
280
    {
281
        if (isset($this->nodes[$name])) {
282
            return $this->nodes[$name];
283
        }
284
285
        foreach ($this->graphs as $graph) {
286
            $node = $graph->findNode($name);
287
            if ($node) {
288
                return $node;
289
            }
290
        }
291
292
        return null;
293
    }
294
295
    /**
296
     * Sets a node using a custom name.
297
     *
298
     * @param string                       $name  Name of the node.
299
     * @param \phpDocumentor\GraphViz\Node $value Node to set on the given name.
300
     *
301
     * @see \phpDocumentor\GraphViz\Graph::setNode()
302
     *
303
     * @return \phpDocumentor\GraphViz\Graph
304
     */
305
    public function __set($name, $value)
306
    {
307
        $this->nodes[$name] = $value;
308
        return $this;
309
    }
310
311
    /**
312
     * Returns the requested node by its name.
313
     *
314
     * @param string $name The name of the node to retrieve.
315
     *
316
     * @see \phpDocumentor\GraphViz\Graph::setNode()
317
     *
318
     * @return \phpDocumentor\GraphViz\Node|null
319
     */
320
    public function __get($name)
321
    {
322
        return isset($this->nodes[$name]) ? $this->nodes[$name] : null;
323
    }
324
325
    /**
326
     * Links two nodes to eachother and registers the Edge onto this graph.
327
     *
328
     * @param \phpDocumentor\GraphViz\Edge $edge The link between two classes.
329
     *
330
     * @see \phpDocumentor\GraphViz\Edge::create()
331
     *
332
     * @return \phpDocumentor\GraphViz\Graph
333
     */
334
    public function link(Edge $edge)
335
    {
336
        $this->edges[] = $edge;
337
        return $this;
338
    }
339
340
    /**
341
     * Exports this graph to a generated image.
342
     *
343
     * This is the only method that actually requires GraphViz.
344
     *
345
     * @param string $type     The type to export to; see the link above for a
346
     *  list of supported types.
347
     * @param string $filename The path to write to.
348
     *
349
     * @uses GraphViz/dot
350
     *
351
     * @link http://www.graphviz.org/content/output-formats
352
     *
353
     * @throws \phpDocumentor\GraphViz\Exception if an error occurred in GraphViz.
354
     *
355
     * @return \phpDocumentor\GraphViz\Graph
356
     */
357 2
    public function export($type, $filename)
358
    {
359 2
        $type = escapeshellarg($type);
360 2
        $filename = escapeshellarg($filename);
361
362
        // write the dot file to a temporary file
363 2
        $tmpfile = tempnam(sys_get_temp_dir(), 'gvz');
364 2
        file_put_contents($tmpfile, (string) $this);
365
366
        // escape the temp file for use as argument
367 2
        $tmpfileArg = escapeshellarg($tmpfile);
368
369
        // create the dot output
370 2
        $output = [];
371 2
        $code = 0;
372
        exec($this->path . "dot -T${type} -o${filename} < ${tmpfileArg} 2>&1", $output, $code);
373
        unlink($tmpfile);
374
375 2
        if ($code !== 0) {
376 1
            throw new Exception(
377
                'An error occurred while creating the graph; GraphViz returned: '
378 1
                . implode(PHP_EOL, $output)
379
            );
380
        }
381
382 1
        return $this;
383
    }
384
385
    /**
386
     * Generates a DOT file for use with GraphViz.
387
     *
388
     * GraphViz is not used in this method; it is safe to call it even without
389
     * GraphViz installed.
390
     *
391
     * @return string
392
     */
393
    public function __toString()
394
    {
395 1
        $elements = array_merge(
396 1
            $this->graphs,
397 1
            $this->attributes,
398 1
            $this->edges,
399 1
            $this->nodes
400
        );
401
402 1
        $attributes = [];
403 1
        foreach ($elements as $value) {
404 1
            $attributes[] = (string) $value;
405
        }
406
407 1
        $attributes = implode(PHP_EOL, $attributes);
408
409 1
        $strict = ($this->isStrict() ? 'strict ' : '');
410
411
        return <<<DOT
412 1
{$strict}{$this->getType()} "{$this->getName()}" {
413 1
${attributes}
414
}
415
DOT;
416
    }
417
}
418