Completed
Pull Request — master (#54)
by
unknown
08:40
created

Graph::isStrict()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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