Store   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 213
Duplicated Lines 0 %

Test Coverage

Coverage 94.83%

Importance

Changes 0
Metric Value
eloc 51
dl 0
loc 213
ccs 55
cts 58
cp 0.9483
rs 10
c 0
b 0
f 0
wmc 18

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getDispatcher() 0 3 1
A statesAreEqual() 0 3 1
A setReducer() 0 8 2
A initialize() 0 3 1
A emitChange() 0 3 1
A setDispatcher() 0 3 1
A dispatch() 0 3 1
A __construct() 0 11 1
A reduce() 0 3 1
A setState() 0 3 1
A subscribe() 0 18 3
A _dispatch() 0 20 3
A getState() 0 3 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Ctefan\Redux\Store;
5
6
use Ctefan\Redux\Action\Action;
7
use Ctefan\Redux\Action\ActionInterface;
8
use Ctefan\Redux\Exception\IsDispatchingException;
9
use Evenement\EventEmitter;
10
use Evenement\EventEmitterInterface;
11
12
class Store implements EnhanceableStoreInterface
13
{
14
    protected const EVENT_CHANGE = 'change';
15
16
    protected const ACTION_TYPE_INIT = '__initialize';
17
18
    /**
19
     * @var array
20
     */
21
    protected $state;
22
23
    /**
24
     * @var callable
25
     */
26
    protected $reducer;
27
28
    /**
29
     * @var callable
30
     */
31
    protected $dispatcher;
32
33
    /**
34
     * @var EventEmitterInterface
35
     */
36
    protected $emitter;
37
38
    /**
39
     * @var bool
40
     */
41
    protected $isDispatching;
42
43
    /**
44
     * Store constructor.
45
     *
46
     * @param callable $reducer
47
     * @param array $initialState
48
     */
49 11
    public function __construct(callable $reducer, array $initialState = [])
50
    {
51 11
        $this->reducer = $reducer;
52 11
        $this->state = $initialState;
53 11
        $this->emitter = new EventEmitter();
54
55
        $this->dispatcher = (function(ActionInterface $action): ActionInterface {
56 11
            return $this->_dispatch($action);
57 11
        })->bindTo($this);
58
59 11
        $this->initialize();
60 11
    }
61
62
    /**
63
     * Dispatch the given action.
64
     *
65
     * @param ActionInterface $action
66
     * @return ActionInterface
67
     */
68 11
    public function dispatch(ActionInterface $action): ActionInterface
69
    {
70 11
        return ($this->dispatcher)($action);
71
    }
72
73
    /**
74
     * Get the current state.
75
     *
76
     * @return mixed
77
     */
78 11
    public function getState(): array
79
    {
80 11
        return $this->state;
81
    }
82
83
    /**
84
     * Add a subscriber callback.
85
     *
86
     * @param callable $callback
87
     * @return callable
88
     * @throws IsDispatchingException
89
     */
90 4
    public function subscribe(callable $callback): callable
91
    {
92 4
        if (true === $this->isDispatching) {
93
            throw new IsDispatchingException();
94
        }
95
96 4
        $this->emitter->on(self::EVENT_CHANGE, $callback);
97 4
        $isSubscribed = true;
98
99
        // Return an unsubscribe callback.
100
        return (function() use (&$isSubscribed, $callback) {
101 2
            if (false === $isSubscribed) {
102 1
                return false;
103
            }
104 2
            $this->emitter->removeListener(self::EVENT_CHANGE, $callback);
105 2
            $isSubscribed = false;
106 2
            return true;
107 4
        })->bindTo($this);
108
    }
109
110
    /**
111
     * Set the reducer to use when dispatching.
112
     *
113
     * @param callable $reducer
114
     * @throws IsDispatchingException
115
     */
116 1
    public function setReducer(callable $reducer): void
117
    {
118 1
        if (true === $this->isDispatching) {
119
            throw new IsDispatchingException();
120
        }
121
122 1
        $this->reducer = $reducer;
123 1
        $this->initialize();
124 1
    }
125
126
    /**
127
     * Get the dispatcher.
128
     *
129
     * @return callable
130
     */
131 3
    public function getDispatcher(): callable
132
    {
133 3
        return $this->dispatcher;
134
    }
135
136
    /**
137
     * Set the dispatcher.
138
     *
139
     * @param callable $dispatcher
140
     */
141 3
    public function setDispatcher(callable $dispatcher): void
142
    {
143 3
        $this->dispatcher = $dispatcher;
144 3
    }
145
146
     /**
147
     * The base dispatcher method.
148
     *
149
     * @param ActionInterface $action
150
     * @return ActionInterface
151
     * @throws IsDispatchingException
152
     */
153 11
    protected function _dispatch(ActionInterface $action): ActionInterface
154
    {
155 11
        if (true === $this->isDispatching) {
156
            throw new IsDispatchingException();
157
        }
158
159
        try {
160 11
            $this->isDispatching = true;
161 11
            $startState = $this->getState();
162 11
            $endState = $this->reduce($startState, $action);
163 11
        } finally {
164 11
            $this->isDispatching = false;
165
        }
166
167 11
        if (false === $this->statesAreEqual($startState, $endState)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $endState does not seem to be defined for all execution paths leading up to this point.
Loading history...
168 6
            $this->setState($endState);
169 6
            $this->emitChange();
170
        }
171
172 11
        return $action;
173
    }
174
175
    /**
176
     * Set the state.
177
     *
178
     * @param $state
179
     */
180 6
    protected function setState($state): void
181
    {
182 6
        $this->state = $state;
183 6
    }
184
185
    /**
186
     * Test whether the given states are equal.
187
     *
188
     * @param $a
189
     * @param $b
190
     * @return bool
191
     */
192 11
    protected function statesAreEqual($a, $b): bool
193
    {
194 11
        return $a == $b;
195
    }
196
197
    /**
198
     * Call the reducer.
199
     *
200
     * @param $state
201
     * @param ActionInterface $action
202
     * @return mixed
203
     */
204 11
    protected function reduce($state, ActionInterface $action)
205
    {
206 11
        return ($this->reducer)($state, $action);
207
    }
208
209
    /**
210
     * Emit a change event.
211
     */
212 6
    protected function emitChange(): void
213
    {
214 6
        $this->emitter->emit(self::EVENT_CHANGE, [$this]);
215 6
    }
216
217
    /**
218
     * Dispatch an initializing action.
219
     *
220
     * @throws IsDispatchingException
221
     */
222 11
    protected function initialize(): void
223
    {
224 11
        $this->dispatch(new Action(self::ACTION_TYPE_INIT));
225
    }
226
}