Completed
Pull Request — master (#124)
by
unknown
04:50 queued 01:06
created

ArrayLoader   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 21
Bugs 3 Features 5
Metric Value
wmc 30
c 21
b 3
f 5
cbo 8
dl 0
loc 248
ccs 107
cts 107
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A load() 0 20 4
A supports() 0 6 2
A findCallbacksByTrigger() 0 15 2
B findCallbacksByTriggerAndPosition() 0 17 8
A loadStates() 0 21 2
B loadTransitions() 0 36 4
A loadCallbacks() 0 12 3
A loadCallbacksFor() 0 21 3
B getCallbacksResolver() 0 27 1
1
<?php
2
3
namespace Finite\Loader;
4
5
use Finite\Event\Callback\Callback;
6
use Finite\Event\Callback\CallbackBuilderFactory;
7
use Finite\Event\Callback\CallbackBuilderFactoryInterface;
8
use Finite\Event\CallbackHandler;
9
use Finite\State\Accessor\PropertyPathStateAccessor;
10
use Finite\StateMachine\StateMachineInterface;
11
use Finite\State\State;
12
use Finite\State\StateInterface;
13
use Finite\Transition\Transition;
14
use Symfony\Component\OptionsResolver\Options;
15
use Symfony\Component\OptionsResolver\OptionsResolver;
16
17
/**
18
 * Loads a StateMachine from an array.
19
 *
20
 * @author Yohan Giarelli <[email protected]>
21
 */
22
class ArrayLoader implements LoaderInterface
0 ignored issues
show
Complexity introduced by
The class ArrayLoader has a coupling between objects value of 14. Consider to reduce the number of dependencies under 13.
Loading history...
23
{
24
    /**
25
     * @var array
26
     */
27
    private $config;
28
29
    /**
30
     * @var CallbackHandler
31
     */
32
    private $callbackHandler;
33
34
    /**
35
     * @var CallbackBuilderFactoryInterface
36
     */
37
    private $callbackBuilderFactory;
38
39
    /**
40
     * @param array                           $config
41
     * @param CallbackHandler                 $handler
42
     * @param CallbackBuilderFactoryInterface $callbackBuilderFactory
43 35
     */
44
    public function __construct(array $config, CallbackHandler $handler = null, CallbackBuilderFactoryInterface $callbackBuilderFactory = null)
45 35
    {
46 35
        $this->callbackHandler = $handler;
47 35
        $this->callbackBuilderFactory = $callbackBuilderFactory;
48
        $this->config = array_merge(
49 35
            array(
50 28
                'class' => '',
51 28
                'graph' => 'default',
52 28
                'property_path' => 'finiteState',
53 28
                'states' => array(),
54 28
                'transitions' => array(),
55
            ),
56 28
            $config
57 35
        );
58
    }
59
60
    /**
61
     * {@inheritdoc}
62 30
     */
63
    public function load(StateMachineInterface $stateMachine)
64 30
    {
65 5
        if (null === $this->callbackHandler) {
66 4
            $this->callbackHandler = new CallbackHandler($stateMachine->getDispatcher());
67
        }
68 30
69 30
        if (null === $this->callbackBuilderFactory) {
70 24
            $this->callbackBuilderFactory = new CallbackBuilderFactory();
71
        }
72 30
73 30
        if (!$stateMachine->hasStateAccessor()) {
74
            $stateMachine->setStateAccessor(new PropertyPathStateAccessor($this->config['property_path']));
75 30
        }
76 30
77 30
        $stateMachine->setGraph($this->config['graph']);
78 30
79
        $this->loadCallbacks($stateMachine);
80
        $this->loadStates($stateMachine);
81
        $this->loadTransitions($stateMachine);
82
    }
83 9
84
    /**
85 9
     * {@inheritdoc}
86
     */
87 5
    public function supports($object, $graph = 'default')
88
    {
89
        $reflection = new \ReflectionClass($this->config['class']);
90
91
        return $reflection->isInstance($object) && $graph === $this->config['graph'];
92
    }
93 30
94
    /**
95 30
     * @param $triggerName
96 30
     *
97 30
     * @return array
98 30
     */
99 30
    protected function findCallbacksByTrigger($triggerName)
100 30
    {
101 24
        $callbacks = [];
102
103 30
        foreach ([Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER] as $position) {
104 25
            $callbacks[$position] = [];
105 25
106 24
            $callbacks[$position] = array_merge(
107 30
                $callbacks[$position],
108
                $this->findCallbacksByTriggerAndPosition($triggerName, $position)
109
            );
110
        }
111
112 30
        return $callbacks;
113
    }
114 30
115 30
    /**
116 30
     * @param $triggerName
117
     * @param $position
118 30
     *
119
     * @return array
120
     */
121
    protected function findCallbacksByTriggerAndPosition($triggerName, $position)
122
    {
123 25
        $callbacks = [];
124
125 25
        foreach ($this->config['callbacks'][$position] as $callbackName => $callback) {
126
            foreach ([Callback::CLAUSE_FROM, Callback::CLAUSE_TO, Callback::CLAUSE_ON] as $clause) {
127 25
                if (!empty($callback[$clause])) {
128 5
                    if ((is_string($callback[$clause]) && $callback[$clause] === $triggerName)
129 4
                        || (is_array($callback[$clause]) && in_array($triggerName, $callback[$clause]))) {
130
                        $callbacks[] = [$callbackName => $callback];
131 25
                    }
132 30
                }
133
            }
134 30
        }
135 25
136 25
        return $callbacks;
137 25
    }
138 20
139 25
    /**
140 25
     * @param StateMachineInterface $stateMachine
141 25
     */
142 25
    private function loadStates(StateMachineInterface $stateMachine)
143 20
    {
144 20
        $resolver = new OptionsResolver();
145 24
        $resolver->setDefaults(array('type' => StateInterface::TYPE_NORMAL, 'properties' => array()));
146 30
        $resolver->setAllowedValues('type', array(
147
            StateInterface::TYPE_INITIAL,
148
            StateInterface::TYPE_NORMAL,
149
            StateInterface::TYPE_FINAL,
150
        ));
151 30
152
        foreach ($this->config['states'] as $state => $config) {
153 30
            $config = $resolver->resolve($config);
154 25
            $stateMachine->addState(new State(
155
                $state,
156
                $config['type'],
157 10
                [],
158 10
                $config['properties'],
159 8
                $this->findCallbacksByTrigger($state)
160 10
            ));
161
        }
162 10
    }
163
164 10
    /**
165 5
     * @param StateMachineInterface $stateMachine
166
     */
167
    private function loadTransitions(StateMachineInterface $stateMachine)
168 10
    {
169 10
        $resolver = new OptionsResolver();
170 10
        $resolver->setRequired(array(Callback::CLAUSE_FROM, Callback::CLAUSE_TO));
171 10
        $resolver->setDefaults(array('guard' => null, 'configure_properties' => null, 'properties' => array()));
172
173 10
        $resolver->setAllowedTypes('configure_properties', array('null', 'callable'));
174 10
175 10
        $resolver->setNormalizer(Callback::CLAUSE_FROM, function (Options $options, $v) { return (array) $v; });
176 10
        $resolver->setNormalizer('guard', function (Options $options, $v) { return !isset($v) ? null : $v; });
177 10
        $resolver->setNormalizer('configure_properties', function (Options $options, $v) {
178 10
            $resolver = new OptionsResolver();
179
180 10
            $resolver->setDefaults($options['properties']);
181 8
182 10
            if (is_callable($v)) {
183
                $v($resolver);
184 10
            }
185
186 10
            return $resolver;
187
        });
188 10
189
        foreach ($this->config['transitions'] as $transition => $config) {
190 10
            $config = $resolver->resolve($config);
191 8
            $stateMachine->addTransition(
192 8
                new Transition(
193
                    $transition,
194 8
                    $config[Callback::CLAUSE_FROM],
195
                    $config['to'],
196 10
                    $config['guard'],
197
                    $config['configure_properties'],
198 10
                    $this->findCallbacksByTrigger($transition)
199 10
                )
200 10
            );
201
        }
202 10
    }
203 10
204 10
    /**
205 10
     * @param StateMachineInterface $stateMachine
206 10
     */
207 10
    private function loadCallbacks(StateMachineInterface $stateMachine)
208
    {
209 10
        if (!isset($this->config['callbacks'])) {
210
            return;
211
        }
212
213
        $stateMachine->setCallbacks($this->config['callbacks']);
214
215
        foreach (array(Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER) as $position) {
216
            $this->loadCallbacksFor($position, $stateMachine);
217
        }
218
    }
219
220
    private function loadCallbacksFor($position, $stateMachine)
221
    {
222
        if (!isset($this->config['callbacks'][$position])) {
223
            return;
224
        }
225
226
        $method = 'add'.ucfirst($position);
227
        $resolver = $this->getCallbacksResolver();
228
        foreach ($this->config['callbacks'][$position] as $specs) {
229
            $specs = $resolver->resolve($specs);
230
231
            $callback = $this->callbackBuilderFactory->createBuilder($stateMachine)
232
                ->setFrom($specs[Callback::CLAUSE_FROM])
233
                ->setTo($specs[Callback::CLAUSE_TO])
234
                ->setOn($specs[Callback::CLAUSE_ON])
235
                ->setCallable($specs[Callback::CLAUSE_DO])
236
                ->getCallback();
237
238
            $this->callbackHandler->$method($callback);
239
        }
240
    }
241
242
    private function getCallbacksResolver()
243
    {
244
        $resolver = new OptionsResolver();
245
246
        $resolver->setDefaults(
247
            array(
248
                Callback::CLAUSE_ON => array(),
249
                Callback::CLAUSE_FROM => array(),
250
                Callback::CLAUSE_TO => array(),
251
            )
252
        );
253
254
        $resolver->setRequired(array(Callback::CLAUSE_DO));
255
256
        $resolver->setAllowedTypes(Callback::CLAUSE_ON,   array('string', 'array'));
257
        $resolver->setAllowedTypes(Callback::CLAUSE_FROM, array('string', 'array'));
258
        $resolver->setAllowedTypes(Callback::CLAUSE_TO,   array('string', 'array'));
259
260
        $toArrayNormalizer = function (Options $options, $value) {
261
            return (array) $value;
262
        };
263
        $resolver->setNormalizer(Callback::CLAUSE_ON,  $toArrayNormalizer);
264
        $resolver->setNormalizer(Callback::CLAUSE_FROM, $toArrayNormalizer);
265
        $resolver->setNormalizer(Callback::CLAUSE_TO,   $toArrayNormalizer);
266
267
        return $resolver;
268
    }
269
}
270