Completed
Push — master ( bac996...b4f0b4 )
by Sébastien
01:04
created

StateMachine::can()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 29

Duplication

Lines 15
Ratio 51.72 %

Importance

Changes 0
Metric Value
dl 15
loc 29
rs 9.1448
c 0
b 0
f 0
cc 5
nc 5
nop 2
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 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
28
        $object,
29
        array $config,
30
        EventDispatcherInterface $dispatcher = null,
31
        CallbackFactoryInterface $callbackFactory = null,
32
        MetadataStoreInterface $metadataStore = null
33
    ) {
34
        parent::__construct($object, $config, $dispatcher, $callbackFactory);
35
36
        $this->metadataStore = $metadataStore ?? new MetadataStore($config);
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function can($transition, array $context = [])
43
    {
44 View Code Duplication
        if (! isset($this->config['transitions'][$transition])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
45
            throw new SMException(sprintf(
46
                'Transition "%s" does not exist on object "%s" with graph "%s".',
47
                $transition,
48
                get_class($this->object),
49
                $this->config['graph']
50
            ));
51
        }
52
53
        if (! in_array($this->getState(), $this->config['transitions'][$transition]['from'])) {
54
            return false;
55
        }
56
57
        $event = new TransitionEvent($transition, $this->getState(), $this->config['transitions'][$transition], $this);
58
59
        $event->setContext($context);
60
61 View Code Duplication
        if (isset($this->dispatcher)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
            $this->dispatcher->dispatch(SMEvents::TEST_TRANSITION, $event);
63
64
            if ($event->isRejected()) {
65
                return false;
66
            }
67
        }
68
69
        return $this->callCallbacks($event, 'guard');
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function apply($transition, $soft = false, array $context = [])
76
    {
77
        if (! $this->can($transition, $context)) {
78
            if ($soft) {
79
                return false;
80
            }
81
82
            throw new SMException(sprintf(
83
                'Transition "%s" cannot be applied on state "%s" of object "%s" with graph "%s".',
84
                $transition,
85
                $this->getState(),
86
                get_class($this->object),
87
                $this->config['graph']
88
            ));
89
        }
90
91
        $event = new TransitionEvent($transition, $this->getState(), $this->config['transitions'][$transition], $this);
92
93
        $event->setContext($context);
94
95 View Code Duplication
        if (isset($this->dispatcher)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
            $this->dispatcher->dispatch(SMEvents::PRE_TRANSITION, $event);
97
98
            if ($event->isRejected()) {
99
                return false;
100
            }
101
        }
102
103
        $this->callCallbacks($event, 'before');
104
105
        $this->setState($this->config['transitions'][$transition]['to']);
106
107
        $this->callCallbacks($event, 'after');
108
109
        if (isset($this->dispatcher)) {
110
            $this->dispatcher->dispatch(SMEvents::POST_TRANSITION, $event);
111
        }
112
113
        return true;
114
    }
115
116
    /**
117
     * Set a new state to the underlying object.
118
     *
119
     * @param string $state
120
     *
121
     * @throws \SM\SMException
122
     */
123
    protected function setState($state)
124
    {
125 View Code Duplication
        if (! $this->hasState($state)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
            throw new SMException(sprintf(
127
                'Cannot set the state to "%s" to object "%s" with graph "%s" because it is not pre-defined.',
128
                $state,
129
                get_class($this->object),
130
                $this->config['graph']
131
            ));
132
        }
133
134
        $accessor = new PropertyAccessor();
135
        $accessor->setValue($this->object, $this->config['property_path'], $state);
136
    }
137
138
    /**
139
     * Check if the graph has the given state.
140
     *
141
     * @param  string $state
142
     * @return bool
143
     */
144
    protected function hasState($state)
145
    {
146
        foreach ($this->config['states'] as $value) {
147
            if ($value['name'] == $state) {
148
                return true;
149
            }
150
        }
151
152
        return false;
153
    }
154
155
    /**
156
     * Get the metadata.
157
     *
158
     * @return \Sebdesign\SM\Metadata\MetadataStoreInterface
159
     */
160
    public function metadata($type = null, $subject = null, $key = null, $default = null)
161
    {
162
        if (is_null($type)) {
163
            return $this->metadataStore;
164
        }
165
166
        switch ($type) {
167
            case 'graph':
168
                return $this->getGraphMetadata($subject, $key);
169
            case 'state':
170
                return $this->getStateMetadata($subject, $key, $default);
171
            case 'transition':
172
                return $this->getTransitionMetadata($subject, $key, $default);
173
            default:
174
                return $this->getGraphMetadata($type, $subject);
175
        }
176
    }
177
178
    protected function getGraphMetadata($key, $default)
179
    {
180
        return $this->metadataStore->graph($key, $default);
181
    }
182
183
    protected function getStateMetadata($subject, $key, $default)
184
    {
185
        if ($this->hasState($subject)) {
186
            return $this->metadataStore->state($subject, $key, $default);
187
        }
188
189
        if (is_null($subject)) {
190
            return $this->metadataStore->state($this->getState(), $key, $default);
191
        }
192
193
        return $this->metadataStore->state($this->getState(), $subject, $key);
194
    }
195
196
    protected function getTransitionMetadata($subject, $key, $default)
197
    {
198
        return $this->metadataStore->transition($subject, $key, $default);
199
    }
200
}
201