StateMachine::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 3
b 1
f 0
nc 1
nop 5
dl 0
loc 10
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Sebdesign\SM\StateMachine;
4
5
use Sebdesign\SM\Event\TransitionEvent;
6
use Sebdesign\SM\Metadata\MetadataStore;
7
use Sebdesign\SM\Metadata\MetadataStoreInterface;
8
use SM\Callback\CallbackFactoryInterface;
9
use SM\Event\SMEvents;
10
use SM\SMException;
11
use SM\StateMachine\StateMachine as BaseStateMachine;
12
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
13
use Symfony\Component\PropertyAccess\PropertyAccessor;
14
15
class StateMachine extends BaseStateMachine
16
{
17
    /**
18
     * @var \Sebdesign\SM\Metadata\MetadataStoreInterface
19
     */
20
    protected $metadataStore;
21
22
    /**
23
     * {@inheritdoc}
24
     *
25
     * @param  \Sebdesign\SM\Metadata\MetadataStoreInterface|null  $metadataStore
26
     */
27 57
    public function __construct(
28
        $object,
29
        array $config,
30
        ?EventDispatcherInterface $dispatcher = null,
31
        ?CallbackFactoryInterface $callbackFactory = null,
32
        ?MetadataStoreInterface $metadataStore = null
33
    ) {
34 57
        parent::__construct($object, $config, $dispatcher, $callbackFactory);
35
36 57
        $this->metadataStore = $metadataStore ?? new MetadataStore($config);
37 19
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 36
    public function can($transition, array $context = []): bool
43
    {
44 36
        if (! isset($this->config['transitions'][$transition])) {
45 3
            throw new SMException(sprintf(
46 3
                'Transition "%s" does not exist on object "%s" with graph "%s".',
47 2
                $transition,
48 3
                get_class($this->object),
49 3
                $this->config['graph']
50 2
            ));
51
        }
52
53 36
        if (! in_array($this->getState(), $this->config['transitions'][$transition]['from'])) {
54 6
            return false;
55
        }
56
57 36
        $event = new TransitionEvent($transition, $this->getState(), $this->config['transitions'][$transition], $this);
58
59 36
        $event->setContext($context);
60
61 36
        if (isset($this->dispatcher)) {
62 36
            $this->dispatcher->dispatch($event, SMEvents::TEST_TRANSITION);
0 ignored issues
show
Bug introduced by
The method dispatch() does not exist on null. ( Ignorable by Annotation )

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

62
            $this->dispatcher->/** @scrutinizer ignore-call */ 
63
                               dispatch($event, SMEvents::TEST_TRANSITION);

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...
63
64 36
            if ($event->isRejected()) {
65 3
                return false;
66
            }
67
        }
68
69 33
        return $this->callCallbacks($event, 'guard');
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 15
    public function apply($transition, $soft = false, array $context = []): bool
76
    {
77 15
        if (! $this->can($transition, $context)) {
78 3
            if ($soft) {
79 3
                return false;
80
            }
81
82 3
            throw new SMException(sprintf(
83 3
                'Transition "%s" cannot be applied on state "%s" of object "%s" with graph "%s".',
84 2
                $transition,
85 3
                $this->getState(),
86 3
                get_class($this->object),
87 3
                $this->config['graph']
88 2
            ));
89
        }
90
91 15
        $event = new TransitionEvent($transition, $this->getState(), $this->config['transitions'][$transition], $this);
92
93 15
        $event->setContext($context);
94
95 15
        if (isset($this->dispatcher)) {
96 15
            $this->dispatcher->dispatch($event, SMEvents::PRE_TRANSITION);
97
98 15
            if ($event->isRejected()) {
99 3
                return false;
100
            }
101
        }
102
103 12
        $this->callCallbacks($event, 'before');
104
105 12
        $this->setState($this->config['transitions'][$transition]['to']);
106
107 9
        $this->callCallbacks($event, 'after');
108
109 9
        if (isset($this->dispatcher)) {
110 9
            $this->dispatcher->dispatch($event, SMEvents::POST_TRANSITION);
111
        }
112
113 9
        return true;
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     */
119 42
    public function getState(): string
120
    {
121 42
        $accessor = new PropertyAccessor();
122 42
        $state = $accessor->getValue($this->object, $this->config['property_path']);
123
124 42
        if ($state instanceof \BackedEnum) {
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
125
            return $state->value;
126
        }
127
128 42
        if ($state instanceof \UnitEnum) {
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
129
            return $state->name;
130
        }
131
132 42
        return $state ?? '';
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 12
    protected function setState($state): void
139
    {
140 12
        if (! $this->hasState($state)) {
141 3
            throw new SMException(sprintf(
142 3
                'Cannot set the state to "%s" to object "%s" with graph "%s" because it is not pre-defined.',
143 2
                $state,
144 3
                get_class($this->object),
145 3
                $this->config['graph']
146 2
            ));
147
        }
148
149 9
        $accessor = new PropertyAccessor();
150 9
        $accessor->setValue($this->object, $this->config['property_path'], $state);
151 3
    }
152
153
    /**
154
     * Check if the graph has the given state.
155
     *
156
     * @param  string  $state
157
     * @return bool
158
     */
159 18
    protected function hasState($state)
160
    {
161 18
        foreach ($this->config['states'] as $value) {
162 18
            if ($value['name'] == $state) {
163 12
                return true;
164
            }
165
        }
166
167 9
        return false;
168
    }
169
170
    /**
171
     * Get the metadata.
172
     *
173
     * @param  string|null  $type
174
     * @param  string|null  $subject
175
     * @param  string|null  $key
176
     * @param  mixed  $default
177
     * @return \Sebdesign\SM\Metadata\MetadataStoreInterface
178
     */
179 15
    public function metadata($type = null, $subject = null, $key = null, $default = null)
180
    {
181 15
        if (is_null($type)) {
182 3
            return $this->metadataStore;
183
        }
184
185
        switch ($type) {
186 12
            case 'graph':
187 3
                return $this->getGraphMetadata($subject, $key);
188 12
            case 'state':
189 6
                return $this->getStateMetadata($subject, $key, $default);
190 6
            case 'transition':
191 3
                return $this->getTransitionMetadata($subject, $key, $default);
192
            default:
193 3
                return $this->getGraphMetadata($type, $subject);
194
        }
195
    }
196
197
    /**
198
     * @param  string|null  $key
199
     * @param  mixed  $default
200
     * @return mixed
201
     */
202 3
    protected function getGraphMetadata($key, $default)
203
    {
204 3
        return $this->metadataStore->graph($key, $default);
205
    }
206
207
    /**
208
     * @param  string|null  $subject
209
     * @param  string|null  $key
210
     * @param  mixed  $default
211
     * @return mixed
212
     */
213 6
    protected function getStateMetadata($subject, $key, $default)
214
    {
215 6
        if (is_null($subject)) {
216 6
            return $this->metadataStore->state($this->getState(), $key, $default);
217
        }
218
219 6
        if ($this->hasState($subject)) {
220 3
            return $this->metadataStore->state($subject, $key, $default);
221
        }
222
223 6
        return $this->metadataStore->state($this->getState(), $subject, $key);
224
    }
225
226
    /**
227
     * @param  string|null  $subject
228
     * @param  string|null  $key
229
     * @param  mixed  $default
230
     * @return mixed
231
     */
232 3
    protected function getTransitionMetadata($subject, $key, $default)
233
    {
234 3
        return $this->metadataStore->transition((string) $subject, $key, $default);
235
    }
236
}
237