Completed
Push — v3.3 ( 6c1cb0...8faaca )
by Masiukevich
48:12 queued 46:35
created

IndexProvider   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Test Coverage

Coverage 85.07%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 68
c 1
b 0
f 0
dl 0
loc 263
ccs 57
cts 67
cp 0.8507
rs 10
wmc 15

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A has() 0 19 2
A setupMutex() 0 7 1
A releaseMutex() 0 12 2
A update() 0 26 2
A add() 0 30 3
A remove() 0 21 2
A get() 0 25 2
1
<?php
2
3
/**
4
 * Event Sourcing implementation module.
5
 *
6
 * @author  Maksim Masiukevich <[email protected]>
7
 * @license MIT
8
 * @license https://opensource.org/licenses/MIT
9
 */
10
11
declare(strict_types = 1);
12
13
namespace ServiceBus\EventSourcingModule;
14
15
use function Amp\call;
16
use Amp\Promise;
17
use ServiceBus\EventSourcing\Indexes\IndexKey;
18
use ServiceBus\EventSourcing\Indexes\IndexValue;
19
use ServiceBus\EventSourcing\Indexes\Store\IndexStore;
20
use ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed;
21
use ServiceBus\Mutex\InMemoryMutexFactory;
22
use ServiceBus\Mutex\Lock;
23
use ServiceBus\Mutex\MutexFactory;
24
use ServiceBus\Storage\Common\Exceptions\UniqueConstraintViolationCheckFailed;
25
26
/**
27
 *
28
 */
29
final class IndexProvider
30
{
31
    /**
32
     * @var IndexStore
33
     */
34
    private $store;
35
36
    /**
37
     * Current locks collection.
38
     *
39
     * @psalm-var array<string, \ServiceBus\Mutex\Lock>
40
     *
41
     * @var Lock[]
42
     */
43
    private $locks = [];
44
45
    /**
46
     * Mutex creator.
47
     *
48
     * @var MutexFactory
49
     */
50
    private $mutexFactory;
51
52
    /**
53
     * IndexProvider constructor.
54
     *
55
     * @param IndexStore        $store
56
     * @param MutexFactory|null $mutexFactory
57
     */
58 6
    public function __construct(IndexStore $store, ?MutexFactory $mutexFactory = null)
59
    {
60 6
        $this->store        = $store;
61 6
        $this->mutexFactory = $mutexFactory ?? new InMemoryMutexFactory();
62 6
    }
63
64
    /**
65
     * Receive index value.
66
     *
67
     * @noinspection   PhpDocRedundantThrowsInspection
68
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
69
     *
70
     * @param IndexKey $indexKey
71
     *
72
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
73
     *
74
     * @return Promise<\ServiceBus\EventSourcing\Indexes\IndexValue|null>
75
     */
76 3
    public function get(IndexKey $indexKey): Promise
77
    {
78
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
79 3
        return call(
80
            function(IndexKey $indexKey): \Generator
81
            {
82
                try
83
                {
84 3
                    yield from $this->setupMutex($indexKey);
85
86
                    /** @var IndexValue|null $value */
87 3
                    $value = yield $this->store->find($indexKey);
88
89 3
                    return $value;
90
                }
91
                catch (\Throwable $throwable)
92
                {
93
                    throw IndexOperationFailed::fromThrowable($throwable);
94
                }
95
                finally
96
                {
97 3
                    yield from $this->releaseMutex($indexKey);
98
                }
99 3
            },
100 3
            $indexKey
101
        );
102
    }
103
104
    /**
105
     * Is there a value in the index.
106
     *
107
     * @noinspection   PhpDocRedundantThrowsInspection
108
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
109
     *
110
     * @param IndexKey $indexKey
111
     *
112
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
113
     *
114
     * @return Promise<bool>
115
     */
116 1
    public function has(IndexKey $indexKey): Promise
117
    {
118
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
119 1
        return call(
120
            function(IndexKey $indexKey): \Generator
121
            {
122
                try
123
                {
124
                    /** @var IndexValue|null $value */
125 1
                    $value = yield $this->store->find($indexKey);
126
127 1
                    return null !== $value;
128
                }
129
                catch (\Throwable $throwable)
130
                {
131
                    throw IndexOperationFailed::fromThrowable($throwable);
132
                }
133 1
            },
134 1
            $indexKey
135
        );
136
    }
137
138
    /**
139
     * Add a value to the index. If false, then the value already exists.
140
     *
141
     * @noinspection   PhpDocRedundantThrowsInspection
142
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
143
     *
144
     * @param IndexKey   $indexKey
145
     * @param IndexValue $value
146
     *
147
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
148
     *
149
     * @return Promise<bool>
150
     */
151 5
    public function add(IndexKey $indexKey, IndexValue $value): Promise
152
    {
153
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
154 5
        return call(
155
            function(IndexKey $indexKey, IndexValue $value): \Generator
156
            {
157
                try
158
                {
159 5
                    yield from $this->setupMutex($indexKey);
160
161
                    /** @var int $affectedRows */
162 5
                    $affectedRows = yield $this->store->add($indexKey, $value);
163
164 5
                    return 0 !== $affectedRows;
165
                }
166 1
                catch (UniqueConstraintViolationCheckFailed $exception)
167
                {
168 1
                    return false;
169
                }
170
                catch (\Throwable $throwable)
171
                {
172
                    throw IndexOperationFailed::fromThrowable($throwable);
173
                }
174
                finally
175
                {
176 5
                    yield from $this->releaseMutex($indexKey);
177
                }
178 5
            },
179 5
            $indexKey,
180 5
            $value
181
        );
182
    }
183
184
    /**
185
     * Remove value from index.
186
     *
187
     * @noinspection PhpDocRedundantThrowsInspection
188
     *
189
     * @param IndexKey $indexKey
190
     *
191
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
192
     *
193
     * @return Promise It doesn't return any result
194
     */
195 1
    public function remove(IndexKey $indexKey): Promise
196
    {
197
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
198 1
        return call(
199
            function(IndexKey $indexKey): \Generator
200
            {
201
                try
202
                {
203 1
                    yield from $this->setupMutex($indexKey);
204 1
                    yield $this->store->delete($indexKey);
205
                }
206
                catch (\Throwable $throwable)
207
                {
208
                    throw IndexOperationFailed::fromThrowable($throwable);
209
                }
210
                finally
211 1
                {
212 1
                    yield from $this->releaseMutex($indexKey);
213
                }
214 1
            },
215 1
            $indexKey
216
        );
217
    }
218
219
    /**
220
     * Update value in index.
221
     *
222
     * @noinspection   PhpDocRedundantThrowsInspection
223
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
224
     *
225
     * @param IndexKey   $indexKey
226
     * @param IndexValue $value
227
     *
228
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
229
     *
230
     * @return Promise<bool>
231
     */
232 1
    public function update(IndexKey $indexKey, IndexValue $value): Promise
233
    {
234
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
235 1
        return call(
236
            function(IndexKey $indexKey, IndexValue $value): \Generator
237
            {
238
                try
239
                {
240 1
                    yield from $this->setupMutex($indexKey);
241
242
                    /** @var int $affectedRows */
243 1
                    $affectedRows = yield $this->store->update($indexKey, $value);
244
245 1
                    return 0 !== $affectedRows;
246
                }
247
                catch (\Throwable $throwable)
248
                {
249
                    throw IndexOperationFailed::fromThrowable($throwable);
250
                }
251
                finally
252
                {
253 1
                    yield from $this->releaseMutex($indexKey);
254
                }
255 1
            },
256 1
            $indexKey,
257 1
            $value
258
        );
259
    }
260
261
    /**
262
     * @param IndexKey $indexKey
263
     *
264
     * @return \Generator
265
     */
266 5
    private function setupMutex(IndexKey $indexKey): \Generator
267
    {
268 5
        $mutexKey = createIndexMutex($indexKey);
269 5
        $mutex    = $this->mutexFactory->create($mutexKey);
270
271
        /** @psalm-suppress InvalidPropertyAssignmentValue */
272 5
        $this->locks[$mutexKey] = yield $mutex->acquire();
273 5
    }
274
275
    /**
276
     * @param IndexKey $indexKey
277
     *
278
     * @return \Generator
279
     */
280 5
    private function releaseMutex(IndexKey $indexKey): \Generator
281
    {
282 5
        $mutexKey = createIndexMutex($indexKey);
283
284 5
        if (true === isset($this->locks[$mutexKey]))
285
        {
286
            /** @var Lock $lock */
287 5
            $lock = $this->locks[$mutexKey];
288
289 5
            yield $lock->release();
290
291 5
            unset($this->locks[$mutexKey]);
292
        }
293 5
    }
294
}
295