Completed
Push — v3.0 ( 6feae5...5b9419 )
by Masiukevich
02:22
created

IndexProvider::update()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2.024

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 1
nop 2
dl 0
loc 30
ccs 9
cts 11
cp 0.8182
crap 2.024
rs 9.8666
c 0
b 0
f 0
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
                    /**
87
                     * @psalm-suppress TooManyTemplateParams Wrong Promise template
88
                     *
89
                     * @var IndexValue|null $value
90
                     */
91 3
                    $value = yield $this->store->find($indexKey);
92
93 3
                    return $value;
94
                }
95
                catch (\Throwable $throwable)
96
                {
97
                    throw IndexOperationFailed::fromThrowable($throwable);
98
                }
99
                finally
100
                {
101 3
                    yield from $this->releaseMutex($indexKey);
102
                }
103 3
            },
104 3
            $indexKey
105
        );
106
    }
107
108
    /**
109
     * Is there a value in the index.
110
     *
111
     * @noinspection   PhpDocRedundantThrowsInspection
112
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
113
     *
114
     * @param IndexKey $indexKey
115
     *
116
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
117
     *
118
     * @return Promise<bool>
119
     */
120 1
    public function has(IndexKey $indexKey): Promise
121
    {
122
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
123 1
        return call(
124
            function(IndexKey $indexKey): \Generator
125
            {
126
                try
127
                {
128
                    /**
129
                     * @psalm-suppress TooManyTemplateParams Wrong Promise template
130
                     *
131
                     * @var IndexValue|null $value
132
                     */
133 1
                    $value = yield $this->store->find($indexKey);
134
135 1
                    return null !== $value;
136
                }
137
                catch (\Throwable $throwable)
138
                {
139
                    throw IndexOperationFailed::fromThrowable($throwable);
140
                }
141 1
            },
142 1
            $indexKey
143
        );
144
    }
145
146
    /**
147
     * Add a value to the index. If false, then the value already exists.
148
     *
149
     * @noinspection   PhpDocRedundantThrowsInspection
150
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
151
     *
152
     * @param IndexKey   $indexKey
153
     * @param IndexValue $value
154
     *
155
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
156
     *
157
     * @return Promise<bool>
158
     */
159 5
    public function add(IndexKey $indexKey, IndexValue $value): Promise
160
    {
161
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
162 5
        return call(
163
            function(IndexKey $indexKey, IndexValue $value): \Generator
164
            {
165
                try
166
                {
167 5
                    yield from $this->setupMutex($indexKey);
168
169
                    /**
170
                     * @psalm-suppress TooManyTemplateParams Wrong Promise template
171
                     *
172
                     * @var int $affectedRows
173
                     */
174 5
                    $affectedRows = yield $this->store->add($indexKey, $value);
175
176 5
                    return 0 !== $affectedRows;
177
                }
178 1
                catch (UniqueConstraintViolationCheckFailed $exception)
179
                {
180 1
                    return false;
181
                }
182
                catch (\Throwable $throwable)
183
                {
184
                    throw IndexOperationFailed::fromThrowable($throwable);
185
                }
186
                finally
187
                {
188 5
                    yield from $this->releaseMutex($indexKey);
189
                }
190 5
            },
191 5
            $indexKey,
192 5
            $value
193
        );
194
    }
195
196
    /**
197
     * Remove value from index.
198
     *
199
     * @noinspection PhpDocRedundantThrowsInspection
200
     *
201
     * @param IndexKey $indexKey
202
     *
203
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
204
     *
205
     * @return Promise It doesn't return any result
206
     */
207 1
    public function remove(IndexKey $indexKey): Promise
208
    {
209
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
210 1
        return call(
211
            function(IndexKey $indexKey): \Generator
212
            {
213
                try
214
                {
215 1
                    yield from $this->setupMutex($indexKey);
216 1
                    yield $this->store->delete($indexKey);
217
                }
218
                catch (\Throwable $throwable)
219
                {
220
                    throw IndexOperationFailed::fromThrowable($throwable);
221
                }
222
                finally
223 1
                {
224 1
                    yield from $this->releaseMutex($indexKey);
225
                }
226 1
            },
227 1
            $indexKey
228
        );
229
    }
230
231
    /**
232
     * Update value in index.
233
     *
234
     * @noinspection   PhpDocRedundantThrowsInspection
235
     * @psalm-suppress MixedTypeCoercion Incorrect resolving the value of the promise
236
     *
237
     * @param IndexKey   $indexKey
238
     * @param IndexValue $value
239
     *
240
     * @throws \ServiceBus\EventSourcingModule\Exceptions\IndexOperationFailed
241
     *
242
     * @return Promise<bool>
243
     */
244 1
    public function update(IndexKey $indexKey, IndexValue $value): Promise
245
    {
246
        /** @psalm-suppress InvalidArgument Incorrect psalm unpack parameters (...$args) */
247 1
        return call(
248
            function(IndexKey $indexKey, IndexValue $value): \Generator
249
            {
250
                try
251
                {
252 1
                    yield from $this->setupMutex($indexKey);
253
254
                    /**
255
                     * @psalm-suppress TooManyTemplateParams Wrong Promise template
256
                     *
257
                     * @var int $affectedRows
258
                     */
259 1
                    $affectedRows = yield $this->store->update($indexKey, $value);
260
261 1
                    return 0 !== $affectedRows;
262
                }
263
                catch (\Throwable $throwable)
264
                {
265
                    throw IndexOperationFailed::fromThrowable($throwable);
266
                }
267
                finally
268
                {
269 1
                    yield from $this->releaseMutex($indexKey);
270
                }
271 1
            },
272 1
            $indexKey,
273 1
            $value
274
        );
275
    }
276
277
    /**
278
     * @param IndexKey $indexKey
279
     *
280
     * @return \Generator
281
     */
282 5
    private function setupMutex(IndexKey $indexKey): \Generator
283
    {
284 5
        $mutexKey = createIndexMutex($indexKey);
285 5
        $mutex    = $this->mutexFactory->create($mutexKey);
286
287
        /**
288
         * @psalm-suppress TooManyTemplateParams
289
         * @psalm-suppress InvalidPropertyAssignmentValue
290
         */
291 5
        $this->locks[$mutexKey] = yield $mutex->acquire();
292 5
    }
293
294
    /**
295
     * @param IndexKey $indexKey
296
     *
297
     * @return \Generator
298
     */
299 5
    private function releaseMutex(IndexKey $indexKey): \Generator
300
    {
301 5
        $mutexKey = createIndexMutex($indexKey);
302
303 5
        if (true === isset($this->locks[$mutexKey]))
304
        {
305
            /** @var Lock $lock */
306 5
            $lock = $this->locks[$mutexKey];
307
308
            /** @psalm-suppress TooManyTemplateParams */
309 5
            yield $lock->release();
310
311 5
            unset($this->locks[$mutexKey]);
312
        }
313 5
    }
314
}
315