ArrayLoader::loadStates()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 13
cts 13
cp 1
rs 9.7666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Finite\Loader;
4
5
use Finite\Event\Callback\CallbackBuilderFactory;
6
use Finite\Event\Callback\CallbackBuilderFactoryInterface;
7
use Finite\Event\CallbackHandler;
8
use Finite\State\Accessor\PropertyPathStateAccessor;
9
use Finite\StateMachine\StateMachineInterface;
10
use Finite\State\State;
11
use Finite\State\StateInterface;
12
use Finite\Transition\Transition;
13
use Symfony\Component\OptionsResolver\Options;
14
use Symfony\Component\OptionsResolver\OptionsResolver;
15
16
/**
17
 * Loads a StateMachine from an array.
18
 *
19
 * @author Yohan Giarelli <[email protected]>
20
 */
21
class ArrayLoader implements LoaderInterface
22
{
23
    /**
24
     * @var array
25
     */
26
    private $config;
27
28
    /**
29
     * @var CallbackHandler
30
     */
31
    private $callbackHandler;
32
33
    /**
34
     * @var CallbackBuilderFactoryInterface
35
     */
36
    private $callbackBuilderFactory;
37
38
    /**
39
     * @param array                           $config
40
     * @param CallbackHandler                 $handler
41
     * @param CallbackBuilderFactoryInterface $callbackBuilderFactory
42
     */
43 40
    public function __construct(array $config, CallbackHandler $handler = null, CallbackBuilderFactoryInterface $callbackBuilderFactory = null)
44
    {
45 40
        $this->callbackHandler = $handler;
46 40
        $this->callbackBuilderFactory = $callbackBuilderFactory;
47 40
        $this->config = array_merge(
48
            array(
49 40
                'class' => '',
50 24
                'graph' => 'default',
51 24
                'property_path' => 'finiteState',
52 24
                'states' => array(),
53 24
                'transitions' => array(),
54 24
            ),
55 16
            $config
56 24
        );
57 40
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 35
    public function load(StateMachineInterface $stateMachine)
63
    {
64 35
        if (null === $this->callbackHandler) {
65 5
            $this->callbackHandler = new CallbackHandler($stateMachine->getDispatcher());
66 3
        }
67
68 35
        if (null === $this->callbackBuilderFactory) {
69 35
            $this->callbackBuilderFactory = new CallbackBuilderFactory();
70 21
        }
71
72 35
        if (!$stateMachine->hasStateAccessor()) {
73 20
            $stateMachine->setStateAccessor(new PropertyPathStateAccessor($this->config['property_path']));
74 12
        }
75
76 35
        $stateMachine->setGraph($this->config['graph']);
77
78 35
        $this->loadStates($stateMachine);
79 35
        $this->loadTransitions($stateMachine);
80 35
        $this->loadCallbacks($stateMachine);
81 35
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 5
    public function supports($object, $graph = 'default')
87
    {
88 5
        $reflection = new \ReflectionClass($this->config['class']);
89
90 5
        return $reflection->isInstance($object) && $graph === $this->config['graph'];
91
    }
92
93
    /**
94
     * @param StateMachineInterface $stateMachine
95
     */
96 35
    private function loadStates(StateMachineInterface $stateMachine)
97
    {
98 35
        $resolver = new OptionsResolver();
99 35
        $resolver->setDefaults(array('type' => StateInterface::TYPE_NORMAL, 'properties' => array()));
100 35
        $resolver->setAllowedValues('type', array(
101 35
            StateInterface::TYPE_INITIAL,
102 35
            StateInterface::TYPE_NORMAL,
103 35
            StateInterface::TYPE_FINAL,
104 21
        ));
105
106 35
        foreach ($this->config['states'] as $state => $config) {
107 30
            $config = $resolver->resolve($config);
108 30
            $stateMachine->addState(new State($state, $config['type'], array(), $config['properties']));
109 21
        }
110 35
    }
111
112
    /**
113
     * @param StateMachineInterface $stateMachine
114
     */
115 35
    private function loadTransitions(StateMachineInterface $stateMachine)
116
    {
117 35
        $resolver = new OptionsResolver();
118 35
        $resolver->setRequired(array('from', 'to'));
119 35
        $resolver->setDefaults(array('guard' => null, 'configure_properties' => null, 'properties' => array()));
120
121 35
        $resolver->setAllowedTypes('configure_properties', array('null', 'callable'));
122
123
        $resolver->setNormalizer('from', function (Options $options, $v) { return (array) $v; });
124
        $resolver->setNormalizer('guard', function (Options $options, $v) { return !isset($v) ? null : $v; });
125 7
        $resolver->setNormalizer('configure_properties', function (Options $options, $v) {
126 30
            $resolver = new OptionsResolver();
127
128 30
            $resolver->setDefaults($options['properties']);
129
130 30
            if (is_callable($v)) {
131 5
                $v($resolver);
132 3
            }
133
134 30
            return $resolver;
135 35
        });
136
137 35
        foreach ($this->config['transitions'] as $transition => $config) {
138 30
            $config = $resolver->resolve($config);
139 30
            $stateMachine->addTransition(
140 30
                new Transition(
141 30
                    $transition,
142 30
                    $config['from'],
143 30
                    $config['to'],
144 30
                    $config['guard'],
145 30
                    $config['configure_properties']
146 18
                )
147 18
            );
148 21
        }
149 35
    }
150
151
    /**
152
     * @param StateMachineInterface $stateMachine
153
     */
154 35
    private function loadCallbacks(StateMachineInterface $stateMachine)
155
    {
156 35
        if (!isset($this->config['callbacks'])) {
157 30
            return;
158
        }
159
160 10
        foreach (array('before', 'after') as $position) {
161 10
            $this->loadCallbacksFor($position, $stateMachine);
162 6
        }
163 10
    }
164
165 10
    private function loadCallbacksFor($position, $stateMachine)
166
    {
167 10
        if (!isset($this->config['callbacks'][$position])) {
168 5
            return;
169
        }
170
171 10
        $method = 'add'.ucfirst($position);
172 10
        $resolver = $this->getCallbacksResolver();
173 10
        foreach ($this->config['callbacks'][$position] as $specs) {
174 10
            $specs = $resolver->resolve($specs);
175
176 10
            $callback = $this->callbackBuilderFactory->createBuilder($stateMachine)
177 10
                ->setFrom($specs['from'])
178 10
                ->setTo($specs['to'])
179 10
                ->setOn($specs['on'])
180 10
                ->setCallable($specs['do'])
181 10
                ->getCallback();
182
183 10
            $this->callbackHandler->$method($callback);
184 6
        }
185 10
    }
186
187 10
    private function getCallbacksResolver()
188
    {
189 10
        $resolver = new OptionsResolver();
190
191 10
        $resolver->setDefaults(
192
            array(
193 10
                'on' => array(),
194 6
                'from' => array(),
195 6
                'to' => array(),
196
            )
197 6
        );
198
199 10
        $resolver->setRequired(array('do'));
200
201 10
        $resolver->setAllowedTypes('on',   array('string', 'array'));
202 10
        $resolver->setAllowedTypes('from', array('string', 'array'));
203 10
        $resolver->setAllowedTypes('to',   array('string', 'array'));
204
205 10
        $toArrayNormalizer = function (Options $options, $value) {
206 10
            return (array) $value;
207 10
        };
208 8
        $resolver->setNormalizer('on',  $toArrayNormalizer);
209 8
        $resolver->setNormalizer('from', $toArrayNormalizer);
210 8
        $resolver->setNormalizer('to',   $toArrayNormalizer);
211
212 8
        return $resolver;
213
    }
214
}
215