Completed
Push — master ( eff795...9418cb )
by Oliver
08:18
created

GraphBuilder::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 7
cts 8
cp 0.875
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
crap 2.0078
1
<?php
2
3
namespace Metabor\Statemachine\Graph;
4
5
use Fhaculty\Graph\Graph;
6
use Metabor\Callback\Callback;
7
use Metabor\StringConverter;
8
use MetaborStd\Event\EventInterface;
9
use MetaborStd\Statemachine\StateCollectionInterface;
10
use MetaborStd\Statemachine\StateInterface;
11
use MetaborStd\Statemachine\TransitionInterface;
12
use MetaborStd\StringConverterInterface;
13
14
/**
15
 * @author otischlinger
16
 */
17
class GraphBuilder
18
{
19
    /**
20
     * @var array
21
     */
22
    private $eventLayout = array();
23
24
    /**
25
     * @var array
26
     */
27
    private $stateLayout = array();
28
29
    /**
30
     * @var \SplObjectStorage
31
     */
32
    private $layoutCallback;
33
34
    /**
35
     * @var Graph
36
     */
37
    private $graph;
38
39
    /**
40
     * @var StringConverterInterface
41
     */
42
    private $stringConverter;
43
44
    /**
45
     * @param Graph                    $graph
46
     * @param StringConverterInterface $stringConverter
47
     */
48 1
    public function __construct(Graph $graph, StringConverterInterface $stringConverter = null)
49
    {
50 1
        $this->layoutCallback = new \SplObjectStorage();
51 1
        $this->graph = $graph;
52 1
        if ($stringConverter) {
53 1
            $this->stringConverter = $stringConverter;
54
        } else {
55 1
            $this->stringConverter = new StringConverter();
56
        }
57 1
    }
58
59
    /**
60
     * @link http://www.graphviz.org/doc/info/attrs.html
61
     *
62
     * @param string $flag
63
     * @param scalar $value
64
     * @param array  $layout
65
     */
66
    public function setEventLayout($flag, $value, array $layout)
67
    {
68
        $value = (string) $value;
69
        $this->eventLayout[$flag][$value] = $layout;
70
    }
71
72
    /**
73
     * @link http://www.graphviz.org/doc/info/attrs.html
74
     *
75
     * @param string $flag
76
     * @param scalar $value
77
     * @param array  $layout
78
     */
79
    public function setStateLayout($flag, $value, array $layout)
80
    {
81
        $value = (string) $value;
82
        $this->stateLayout[$flag][$value] = $layout;
83
    }
84
85
    /**
86
     * @param Callback $callback
87
     */
88
    public function attachLayoutCallback(Callback $callback)
89
    {
90
        $this->layoutCallback->attach($callback);
91
    }
92
93
    /**
94
     * @param Callback $callback
95
     */
96
    public function detachLayoutCallback(Callback $callback)
97
    {
98
        $this->layoutCallback->detach($callback);
99
    }
100
101
    /**
102
     * @param \ArrayAccess $flaggedObject
103
     * @param array        $layout
104
     *
105
     * @return array
106
     */
107 1
    protected function getLayoutOptions(\ArrayAccess $flaggedObject, array $layout)
108
    {
109 1
        $result = array();
110 1
        foreach ($layout as $flag => $options) {
111
            if ($flaggedObject->offsetExists($flag)) {
112
                $value = $flaggedObject->offsetGet($flag);
113
                $value = (string) $value;
114
                if (isset($options[$value])) {
115
                    $result += $options[$value];
116
                }
117
            }
118 1
        }
119
120
        /* @var $callback callable */
121 1
        foreach ($this->layoutCallback as $callback) {
122
            $result = $callback($flaggedObject, $result);
123 1
        }
124
125 1
        return $result;
126
    }
127
128
    /**
129
     * @param StateInterface $state
130
     *
131
     * @return \Fhaculty\Graph\Vertex
132
     */
133 1
    public function createStatusVertex(StateInterface $state)
134
    {
135 1
        $stateName = $state->getName();
136 1
        $vertex = $this->graph->createVertex($stateName, true);
137 1
        if ($state instanceof \ArrayAccess) {
138 1
            $layout = $this->getLayoutOptions($state, $this->stateLayout);
139 1
            if (method_exists($vertex, 'setLayout')) {
140 1
                $vertex->setLayout($layout);
0 ignored issues
show
Bug introduced by
The method setLayout() does not seem to exist on object<Fhaculty\Graph\Vertex>.

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...
141 1
            } else {
142
                foreach ($layout as $name => $value) {
143
                    $vertex->setAttribute('graphviz.' . $name, $value);
144
                }
145
            }
146 1
        }
147
148 1
        return $vertex;
149
    }
150
151
    /**
152
     * @param EventInterface $event
153
     *
154
     * @return string
155
     */
156
    protected function convertObserverToString(EventInterface $event)
157
    {
158
        $observers = array();
159
        foreach ($event->getObservers() as $observer) {
160
            $observers[] = $this->stringConverter->convertToString($observer);
161
        }
162
163
        return implode(', ', $observers);
164
    }
165
166
    /**
167
     * @param TransitionInterface $transition
168
     *
169
     * @return string
170
     */
171 1
    protected function getTransitionLabel(StateInterface $state, TransitionInterface $transition)
172
    {
173 1
        $labelParts = array();
174 1
        $eventName = $transition->getEventName();
175 1
        if ($eventName) {
176
            $labelParts[] = 'E: ' . $eventName;
177
            $event = $state->getEvent($eventName);
178
            $observerName = $this->convertObserverToString($event);
179
            if ($observerName) {
180
                $labelParts[] = 'C: ' . $observerName;
181
            }
182
        }
183 1
        $conditionName = $transition->getConditionName();
184 1
        if ($conditionName) {
185
            $labelParts[] = 'IF: ' . $conditionName;
186
        }
187
188 1
        $label = implode(PHP_EOL, $labelParts);
189
190 1
        return $label;
191
    }
192
193
    /**
194
     * @param StateInterface      $state
195
     * @param TransitionInterface $transition
196
     */
197 1
    protected function addTransition(StateInterface $state, TransitionInterface $transition)
198
    {
199 1
        $sourceStateVertex = $this->createStatusVertex($state);
200 1
        $targetStateVertex = $this->createStatusVertex($transition->getTargetState());
201 1
        $edge = $sourceStateVertex->createEdgeTo($targetStateVertex);
202 1
        $label = $this->getTransitionLabel($state, $transition);
203 1
        if ($label) {
204
            if (method_exists($edge, 'setLayoutAttribute')) {
205
                $edge->setLayoutAttribute('label', $label);
0 ignored issues
show
Bug introduced by
The method setLayoutAttribute() does not seem to exist on object<Fhaculty\Graph\Edge\Directed>.

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...
206
            } else {
207
                $edge->setAttribute('graphviz.label', $label);
208
            }
209
        }
210
211 1
        $eventName = $transition->getEventName();
212 1
        if ($eventName) {
213
            $event = $state->getEvent($eventName);
214
            if ($event instanceof \ArrayAccess) {
215
                $layout = $this->getLayoutOptions($event, $this->eventLayout);
216
                if (method_exists($edge, 'setLayout')) {
217
                    $edge->setLayout($layout);
0 ignored issues
show
Bug introduced by
The method setLayout() does not seem to exist on object<Fhaculty\Graph\Edge\Directed>.

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...
218
                } else {
219
                    foreach ($layout as $name => $value) {
220
                        $edge->setAttribute('graphviz.' . $name, $value);
221
                    }
222
                }
223
            }
224
        }
225 1
    }
226
227
    /**
228
     * @param StateInterface $state
229
     */
230 1
    public function addState(StateInterface $state)
231
    {
232 1
        $this->createStatusVertex($state);
233
        /* @var $transition TransitionInterface */
234 1
        foreach ($state->getTransitions() as $transition) {
235 1
            $this->addTransition($state, $transition);
236 1
        }
237 1
    }
238
239
    /**
240
     * @param \Traversable $states
241
     */
242 1
    public function addStates(\Traversable $states)
243
    {
244 1
        foreach ($states as $state) {
245 1
            $this->addState($state);
246 1
        }
247 1
    }
248
249
    /**
250
     * @param StateCollectionInterface $stateCollection
251
     */
252 1
    public function addStateCollection(StateCollectionInterface $stateCollection)
253
    {
254 1
        $this->addStates($stateCollection->getStates());
255 1
    }
256
257
    /**
258
     * @return \Fhaculty\Graph\Graph
259
     */
260
    public function getGraph()
261
    {
262
        return $this->graph;
263
    }
264
}
265