Debug::askForGraph()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 13
ccs 8
cts 8
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Sebdesign\SM\Commands;
4
5
use Illuminate\Console\Command;
6
use Symfony\Component\Console\Helper\TableSeparator;
7
8
class Debug extends Command
9
{
10
    /**
11
     * The name and signature of the console command.
12
     *
13
     * @var string
14
     */
15
    protected $signature = 'winzou:state-machine:debug {graph? : A state machine graph}';
16
17
    /**
18
     * The console command description.
19
     *
20
     * @var string
21
     */
22
    protected $description = 'Show 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 18
    public function __construct(array $config)
32
    {
33 18
        parent::__construct();
34
35 18
        $this->config = $config;
36 6
    }
37
38
    /**
39
     * Execute the console command.
40
     *
41
     * @return mixed
42
     */
43 12
    public function handle()
44
    {
45 12
        if (empty($this->config)) {
46 3
            $this->error('There are no state machines configured.');
47
48 3
            return 1;
49
        }
50
51 9
        if (! $this->argument('graph')) {
52 3
            $this->askForGraph();
53
        }
54
55 9
        $graph = $this->argument('graph');
56
57 9
        if (! array_key_exists($graph, $this->config)) {
0 ignored issues
show
Bug introduced by
It seems like $graph can also be of type array; however, parameter $key of array_key_exists() does only seem to accept integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
        if (! array_key_exists(/** @scrutinizer ignore-type */ $graph, $this->config)) {
Loading history...
58 3
            $this->error('The provided state machine graph is not configured.');
59
60 3
            return 1;
61
        }
62
63 6
        $config = $this->config[$graph];
64
65 6
        $this->printStates($config['states']);
66 6
        $this->printTransitions($config['transitions']);
67
68 6
        if (isset($config['callbacks'])) {
69 6
            $this->printCallbacks($config['callbacks']);
70
        }
71
72 6
        return 0;
73
    }
74
75
    /**
76
     * Ask for a graph name if one was not provided as argument.
77
     */
78 3
    protected function askForGraph()
79
    {
80 2
        $choices = array_map(function ($name, $config) {
81 3
            return $name."\t(".$config['class'].' - '.$config['graph'].')';
82 3
        }, array_keys($this->config), $this->config);
83
84 3
        $choice = $this->choice('Which state machine would you like to know about?', $choices, 0);
85
86 3
        $choice = substr($choice, 0, strpos($choice, "\t"));
0 ignored issues
show
Bug introduced by
It seems like $choice can also be of type array; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        $choice = substr($choice, 0, strpos(/** @scrutinizer ignore-type */ $choice, "\t"));
Loading history...
Bug introduced by
It seems like $choice can also be of type array; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        $choice = substr(/** @scrutinizer ignore-type */ $choice, 0, strpos($choice, "\t"));
Loading history...
87
88 3
        $this->info('You have just selected: '.$choice);
89
90 3
        $this->input->setArgument('graph', $choice);
91 1
    }
92
93
    /**
94
     * Display the graph states on a table.
95
     *
96
     * @param  array  $states
97
     */
98 6
    protected function printStates(array $states)
99
    {
100 4
        $this->table(['Configured States:', 'Metadata:'], array_map(function ($state) {
101 6
            if (is_null($state)) {
102
                return [$state, null];
103
            }
104
105 6
            if (is_string($state)) {
106 6
                return [$state, null];
107
            }
108
109 6
            if (isset($state['metadata'])) {
110 6
                $metadata = json_encode($state['metadata'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
111
            } else {
112 6
                $metadata = null;
113
            }
114
115 6
            return [$state['name'], $metadata];
116 6
        }, $states));
117 2
    }
118
119
    /**
120
     * Display the graph transitions on a table.
121
     *
122
     * @param  array  $transitions
123
     */
124 6
    protected function printTransitions(array $transitions)
125
    {
126 6
        end($transitions);
127
128 6
        $lastTransition = key($transitions);
129
130 6
        reset($transitions);
131
132 6
        $rows = [];
133
134 6
        foreach ($transitions as $name => $transition) {
135 6
            $rows[] = [$name, implode(PHP_EOL, $transition['from']), $transition['to']];
136
137 6
            if ($name !== $lastTransition) {
138 6
                $rows[] = new TableSeparator();
139
            }
140
        }
141
142 6
        $this->table(['Transition', 'From(s)', 'To'], $rows);
143 2
    }
144
145
    /**
146
     * Display the graph callbacks on a table.
147
     *
148
     * @param  array  $allCallbacks
149
     */
150 6
    protected function printCallbacks(array $allCallbacks)
151
    {
152 6
        foreach ($allCallbacks as $position => $callbacks) {
153 6
            $rows = [];
154 6
            foreach ($callbacks as $name => $specs) {
155 6
                $rows[] = [
156 6
                    $name,
157 6
                    $this->formatSatisfies($specs),
158 6
                    $this->formatCallable($specs),
159 6
                    $this->formatClause($specs, 'args'),
160 4
                ];
161
            }
162
163 6
            $this->table([ucfirst($position).' Callbacks', 'Satisfies', 'Do', 'Args'], $rows);
164
        }
165 2
    }
166
167
    /**
168
     * Format the clauses that satisfy the callback.
169
     *
170
     * @param  array  $specs
171
     * @return string
172
     */
173 6
    protected function formatSatisfies(array $specs)
174
    {
175 4
        $clauses = array_map(function ($clause) use ($specs) {
176 6
            if ($result = $this->formatClause($specs, $clause)) {
177 6
                return vsprintf('%s: %s', [
178 6
                    ucfirst(str_replace('_', ' ', $clause)),
179 6
                    $result,
180 4
                ]);
181
            }
182 6
        }, ['from', 'excluded_from', 'on', 'excluded_on', 'to', 'excluded_to']);
183
184 6
        return implode(PHP_EOL, array_filter($clauses));
185
    }
186
187
    /**
188
     * Format the callback clause.
189
     *
190
     * @param  array  $specs
191
     * @param  string  $clause
192
     * @return string
193
     */
194 6
    protected function formatClause(array $specs, $clause)
195
    {
196 6
        if (isset($specs[$clause])) {
197 6
            return implode(', ', (array) $specs[$clause]);
198
        }
199
200 6
        return '';
201
    }
202
203
    /**
204
     * Format the callable callable.
205
     *
206
     * @param  array  $specs
207
     * @return string
208
     */
209 6
    protected function formatCallable(array $specs)
210
    {
211 6
        if (isset($specs['can'])) {
212 6
            $callback = json_encode($specs['can']);
213
214 6
            return "Gate::check({$callback})";
215
        }
216
217 6
        if (! isset($specs['do'])) {
218
            return '';
219
        }
220
221 6
        $callback = $specs['do'];
222
223 6
        if ($callback instanceof \Closure) {
224
            return 'Closure';
225
        }
226
227 6
        if (is_string($callback)) {
228
            if (strpos($callback, '@') !== false) {
229
                $callback = explode('@', $callback);
230
            } else {
231
                return $callback.'()';
232
            }
233
        }
234
235 6
        if (is_array($callback)) {
236 6
            return implode('::', $callback).'()';
237
        }
238
239
        return $callback;
240
    }
241
}
242