Completed
Pull Request — master (#32)
by
unknown
01:12
created

Visualize::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Sebdesign\SM\Commands;
4
5
use Illuminate\Console\Command;
6
use Symfony\Component\Process\Process;
7
8
class Visualize extends Command
9
{
10
    /**
11
     * The name and signature of the console command.
12
     *
13
     * @var string
14
     */
15
    protected $signature = 'winzou:state-machine:visualize {graph? : A state machine graph} {--output=./graph.jpg} {--format=jpg} {--direction=TB} {--shape=circle} {--dot-path=/usr/local/bin/dot}';
16
17
    /**
18
     * The console command description.
19
     *
20
     * @var string
21
     */
22
    protected $description = 'Generates an image of the states and transitions of state machine graphs';
23
24
    protected $config;
25
26
    /**
27
     * Create a new command instance.
28
     *
29
     * @param array $config
30
     */
31
    public function __construct(array $config)
32
    {
33
        parent::__construct();
34
35
        $this->config = $config;
36
    }
37
38
    /**
39
     * Execute the console command.
40
     *
41
     * @return mixed
42
     */
43
    public function handle()
44
    {
45
        if (empty($this->config)) {
46
            $this->error('There are no state machines configured.');
47
48
            return 1;
49
        }
50
51
        if (! $this->argument('graph')) {
52
            $this->askForGraph();
0 ignored issues
show
Documentation Bug introduced by
The method askForGraph does not exist on object<Sebdesign\SM\Commands\Visualize>? 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...
53
        }
54
55
        $graph = $this->argument('graph');
56
57
        if (! array_key_exists($graph, $this->config)) {
58
            $this->error('The provided state machine graph is not configured.');
59
60
            return 1;
61
        }
62
63
        $config = $this->config[$graph];
64
65
        $this->stateMachineInDotFormat($config);
66
67
        return 0;
68
    }
69
70
    protected function stateMachineInDotFormat(array $config)
71
    {
72
        // Output image mime types.
73
        $mimeTypes = [
74
            'png' => 'image/png',
75
            'jpg' => 'image/jpeg',
76
            'gif' => 'image/gif',
77
            'svg' => 'image/svg+xml',
78
        ];
79
80
        $format = $this->option('format');
81
82
        if (empty($mimeTypes[$format])) {
83
            throw new \Exception(sprintf("Format '%s' is not supported", $format));
84
        }
85
86
        $dotPath = $this->option('dot-path');
87
88
        // Temporary files.
89
        $dotFile = tempnam(sys_get_temp_dir(), 'smv');
90
        $outputImage = $this->option('output');
91
92
        // Display settings
93
        $layout = $this->option('direction');
94
        $layout = $layout === 'TB' ? 'TB' : 'LR';
95
96
        $nodeShape = $this->option('shape');
97
98
        // Build dot file content.
99
        $result = [];
100
        $result[] = 'digraph finite_state_machine {';
101
        $result[] = "rankdir=$layout;";
102
        $result[] = 'node [shape = point]; _start_'; // Input node
103
104
        // Use first value from 'states' as start.
105
        $start = $config['states'][0]['name'];
106
        $result[] = "node [shape = $nodeShape];"; // Default nodes
107
        $result[] = '_start_ -> '.$start.';'; // Input node -> starting node.
108
109
        foreach ($config['transitions'] as $name => $transition) {
110
            foreach ($transition['from'] as $from) {
111
                // TODO: Add customization.
112
                $result[] = $from.' -> '.$transition['to'].'[ label = "'.$name.'" ];';
113
            }
114
        }
115
116
        $result[] = '}';
117
118
        $result = implode(PHP_EOL, $result);
119
120
        // Save dot file for input.
121
        file_put_contents($dotFile, $result);
122
123
        // Dot command.
124
        $cmd = sprintf(
125
            "%s -T%s -o %s %s",
126
            $dotPath,
127
            $format,
128
            $outputImage, // Output file
129
            $dotFile // Input file
130
        );
131
132
133
        $process = new Process($cmd);
134
        $process->run();
135
    }
136
}
137