RedisRepository::create()   B
last analyzed

Complexity

Conditions 11
Paths 3

Size

Total Lines 69
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 39
nc 3
nop 3
dl 0
loc 69
rs 7.3166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the Simple EventStore Manager package.
4
 *
5
 * (c) Mauro Cassani<https://github.com/mauretto78>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace InMemoryList\Infrastructure\Persistance;
12
13
use InMemoryList\Domain\Helper\ListElementConsistencyChecker;
14
use InMemoryList\Domain\Model\Contracts\ListRepositoryInterface;
15
use InMemoryList\Domain\Model\Exceptions\ListElementNotConsistentException;
16
use InMemoryList\Domain\Model\ListCollection;
17
use InMemoryList\Domain\Model\ListElement;
18
use InMemoryList\Domain\Model\ListElementUuid;
19
use InMemoryList\Infrastructure\Persistance\Exceptions\ListAlreadyExistsException;
20
use InMemoryList\Infrastructure\Persistance\Exceptions\ListDoesNotExistsException;
21
use Predis\Client;
22
23
class RedisRepository extends AbstractRepository implements ListRepositoryInterface
24
{
25
    /**
26
     * @var Client
27
     */
28
    private $client;
29
30
    /**
31
     * RedisRepository constructor.
32
     *
33
     * @param Client $client
34
     */
35
    public function __construct(Client $client)
36
    {
37
        $this->client = $client;
38
    }
39
40
    /**
41
     * @param ListCollection $list
42
     * @param null           $ttl
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $ttl is correct as it would always require null to be passed?
Loading history...
43
     * @param null           $chunkSize
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $chunkSize is correct as it would always require null to be passed?
Loading history...
44
     *
45
     * @return mixed
46
     *
47
     * @throws ListAlreadyExistsException
48
     */
49
    public function create(ListCollection $list, $ttl = null, $chunkSize = null)
50
    {
51
        // check if list already exists in memory
52
        $listUuid = (string) $list->getUuid();
53
        if ($this->existsListInIndex($listUuid) && $this->exists($listUuid)) {
54
            throw new ListAlreadyExistsException('List '.$list->getUuid().' already exists in memory.');
55
        }
56
57
        if (!$chunkSize && !is_int($chunkSize)) {
0 ignored issues
show
introduced by
The condition is_int($chunkSize) is always false.
Loading history...
introduced by
$chunkSize is of type null, thus it always evaluated to false.
Loading history...
58
            $chunkSize = self::CHUNKSIZE;
59
        }
60
61
        $items = $list->getElements();
62
63
        // persist in memory array in chunks
64
        $arrayChunks = array_chunk($items, $chunkSize, true);
65
66
        $options = [
67
            'cas' => true,
68
            'watch' => $this->getArrayChunksKeys($listUuid, count($arrayChunks)),
69
            'retry' => 3,
70
        ];
71
72
        // persist all in a transaction
73
        $this->client->transaction($options, function ($tx) use ($arrayChunks, $list, $ttl, $listUuid, $items, $chunkSize) {
74
            foreach ($arrayChunks as $chunkNumber => $item) {
75
                foreach ($item as $key => $element) {
76
                    $listChunkUuid = $listUuid.self::SEPARATOR.self::CHUNK.'-'.($chunkNumber + 1);
77
                    $elementUuid = $element->getUuid();
78
                    $body = $element->getBody();
79
80
                    $tx->hset(
81
                        (string) $listChunkUuid,
82
                        (string) $elementUuid,
83
                        (string) $body
84
                    );
85
86
                    // set ttl
87
                    if ($ttl) {
88
                        $tx->expire(
89
                            (string) $listChunkUuid,
90
                            $ttl
91
                        );
92
                    }
93
                }
94
            }
95
96
            // set headers
97
            if ($list->getHeaders()) {
98
                foreach ($list->getHeaders() as $key => $header) {
99
                    $this->client->hset(
100
                        $listUuid.self::SEPARATOR.self::HEADERS,
101
                        $key,
102
                        $header
103
                    );
104
                }
105
106
                if ($ttl) {
0 ignored issues
show
introduced by
$ttl is of type null, thus it always evaluated to false.
Loading history...
107
                    $this->client->expire($listUuid.self::SEPARATOR.self::HEADERS, $ttl);
108
                }
109
            }
110
111
            // add list to index
112
            $this->addOrUpdateListToIndex(
113
                $listUuid,
114
                (int) count($items),
115
                (int) count($arrayChunks),
116
                (int) $chunkSize,
117
                $ttl
118
            );
119
        });
120
    }
121
122
    /**
123
     * @param $listUuid
124
     * @param $numberOfChunks
125
     *
126
     * @return array
127
     */
128
    private function getArrayChunksKeys($listUuid, $numberOfChunks)
129
    {
130
        $arrayChunksKeys = [];
131
        for ($i = 0; $i < $numberOfChunks; ++$i) {
132
            $arrayChunksKeys[] = $listUuid.self::SEPARATOR.self::CHUNK.'-'.($i + 1);
133
        }
134
135
        return $arrayChunksKeys;
136
    }
137
138
    /**
139
     * @param $listUuid
140
     * @param $elementUuid
141
     *
142
     * @return mixed
143
     */
144
    public function deleteElement($listUuid, $elementUuid)
145
    {
146
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
147
        $chunkSize = $this->getChunkSize($listUuid);
148
149
        $options = [
150
            'cas' => true,
151
            'watch' => $this->getArrayChunksKeys($listUuid, $numberOfChunks),
152
            'retry' => 3,
153
        ];
154
155
        // delete in a transaction
156
        $this->client->transaction($options, function ($tx) use ($listUuid, $numberOfChunks, $chunkSize, $elementUuid) {
157
            for ($i = 1; $i <= $numberOfChunks; ++$i) {
158
                $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
159
                $chunk = $this->client->hgetall($chunkNumber);
160
161
                if (array_key_exists($elementUuid, $chunk)) {
162
                    // delete elements from chunk
163
                    $tx->hdel($chunkNumber, $elementUuid);
164
165
                    // update list index
166
                    $prevIndex = $this->getIndex($listUuid);
167
                    $this->addOrUpdateListToIndex(
168
                        $listUuid,
169
                        ($prevIndex['size'] - 1),
170
                        $numberOfChunks,
171
                        $chunkSize,
172
                        $prevIndex['ttl']
173
                    );
174
175
                    // delete headers if counter = 0
176
                    $headersKey = $listUuid.self::SEPARATOR.self::HEADERS;
177
                    if ($this->getCounter($listUuid) === 0) {
178
                        $tx->del($headersKey);
179
                    }
180
181
                    break;
182
                }
183
            }
184
        });
185
    }
186
187
    /**
188
     * @param $listUuid
189
     * @param $size
190
     * @param $numberOfChunks
191
     * @param null $ttl
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $ttl is correct as it would always require null to be passed?
Loading history...
192
     */
193
    private function addOrUpdateListToIndex($listUuid, $size, $numberOfChunks, $chunkSize, $ttl = null)
194
    {
195
        $indexKey = ListRepositoryInterface::INDEX;
196
        $this->client->hset(
197
            $indexKey,
198
            (string) $listUuid,
199
            serialize([
200
                'uuid' => $listUuid,
201
                'created_on' => new \DateTimeImmutable(),
202
                'size' => $size,
203
                'chunks' => $numberOfChunks,
204
                'chunk-size' => $chunkSize,
205
                'headers' => $this->getHeaders($listUuid),
206
                'ttl' => $ttl,
207
            ])
208
        );
209
210
        if ($size === 0) {
211
            $this->removeListFromIndex((string) $listUuid);
212
        }
213
    }
214
215
    /**
216
     * @param $listUuid
217
     *
218
     * @return bool
219
     */
220
    public function exists($listUuid)
221
    {
222
        $listFirstChunk = $this->client->hgetall($listUuid.self::SEPARATOR.self::CHUNK.'-1');
223
224
        return (count($listFirstChunk) === 0) ? false : true;
225
    }
226
227
    /**
228
     * @param $listUuid
229
     *
230
     * @return mixed
231
     */
232
    public function findListByUuid($listUuid)
233
    {
234
        $collection = $this->client->hgetall($listUuid.self::SEPARATOR.self::CHUNK.'-1');
235
        $number = $this->getNumberOfChunks($listUuid);
236
237
        for ($i = 2; $i <= $number; ++$i) {
238
            $collection = array_merge($collection, $this->client->hgetall($listUuid.self::SEPARATOR.self::CHUNK.'-'.$i));
239
        }
240
241
        return array_map('unserialize', $collection);
242
    }
243
244
    /**
245
     * @return mixed
246
     */
247
    public function flush()
248
    {
249
        $this->client->flushall();
250
    }
251
252
    /**
253
     * @param $listUuid
254
     *
255
     * @return array
256
     */
257
    public function getHeaders($listUuid)
258
    {
259
        return $this->client->hgetall($listUuid.self::SEPARATOR.self::HEADERS);
260
    }
261
262
    /**
263
     * @param null $listUuid
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $listUuid is correct as it would always require null to be passed?
Loading history...
264
     *
265
     * @return array
266
     */
267
    public function getIndex($listUuid = null)
268
    {
269
        $indexKey = ListRepositoryInterface::INDEX;
270
        $index = $this->client->hgetall($indexKey);
271
        $this->removeExpiredListsFromIndex($index);
272
273
        if ($listUuid) {
0 ignored issues
show
introduced by
$listUuid is of type null, thus it always evaluated to false.
Loading history...
274
            return (isset($index[(string) $listUuid])) ? unserialize($this->client->hget($indexKey, $listUuid)) : null;
275
        }
276
277
        return array_map('unserialize', $this->client->hgetall($indexKey));
278
    }
279
280
    /**
281
     * @return array
282
     */
283
    public function getStatistics()
284
    {
285
        return $this->client->info();
286
    }
287
288
    /**
289
     * @param $listUuid
290
     * @param ListElement $listElement
291
     *
292
     * @throws ListElementNotConsistentException
293
     *
294
     * @return mixed
295
     */
296
    public function pushElement($listUuid, ListElement $listElement)
297
    {
298
        $elementUuid = $listElement->getUuid();
299
        $body = $listElement->getBody();
300
301
        if (!ListElementConsistencyChecker::isConsistent($listElement, $this->findListByUuid($listUuid))) {
302
            throw new ListElementNotConsistentException('Element '.(string) $listElement->getUuid().' is not consistent with list data.');
303
        }
304
305
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
306
        $chunkSize = $this->getChunkSize($listUuid);
307
308
        $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
309
310
        $options = [
311
            'cas' => true,
312
            'watch' => $this->getArrayChunksKeys($listUuid, $numberOfChunks),
313
            'retry' => 3,
314
        ];
315
316
        // persist in a transaction
317
        $this->client->transaction($options, function ($tx) use ($chunkNumber, $numberOfChunks, $chunkSize, $listUuid, $elementUuid, $body) {
318
            if (($chunkSize - count($tx->hgetall($chunkNumber))) === 0) {
319
                ++$numberOfChunks;
320
                $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
321
            }
322
323
            $tx->hset(
324
                (string) $chunkNumber,
325
                (string) $elementUuid,
326
                (string) $body
327
            );
328
329
            // update list index
330
            $prevIndex = $this->getIndex($listUuid);
331
            $this->addOrUpdateListToIndex(
332
                $listUuid,
333
                ($prevIndex['size'] + 1),
334
                $numberOfChunks,
335
                $chunkSize,
336
                $this->getTtl($listUuid)
337
            );
338
        });
339
    }
340
341
    /**
342
     * @param $listUuid
343
     *
344
     * @return mixed
345
     */
346
    public function removeListFromIndex($listUuid)
347
    {
348
        $this->client->hdel(
349
            ListRepositoryInterface::INDEX,
350
            $listUuid
351
        );
352
    }
353
354
    /**
355
     * @param $listUuid
356
     * @param $elementUuid
357
     * @param array $data
358
     *
359
     * @throws ListElementNotConsistentException
360
     *
361
     * @return mixed
362
     */
363
    public function updateElement($listUuid, $elementUuid, $data)
364
    {
365
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
366
367
        $options = [
368
            'cas' => true,
369
            'watch' => $this->getArrayChunksKeys($listUuid, $numberOfChunks),
370
            'retry' => 3,
371
        ];
372
373
        // persist in a transaction
374
        $this->client->transaction($options, function ($tx) use ($numberOfChunks, $listUuid, $elementUuid, $data) {
375
            for ($i = 1; $i <= $numberOfChunks; ++$i) {
376
                $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
377
                $chunk = $this->client->hgetall($chunkNumber);
378
379
                if (array_key_exists($elementUuid, $chunk)) {
380
                    $listElement = $this->findElement(
381
                        (string) $listUuid,
382
                        (string) $elementUuid
383
                    );
384
385
                    $updatedElementBody = $this->updateListElementBody($listElement, $data);
386
387
                    if (!ListElementConsistencyChecker::isConsistent($updatedElementBody, $this->findListByUuid($listUuid))) {
388
                        throw new ListElementNotConsistentException('Element '.(string) $elementUuid.' is not consistent with list data.');
389
                    }
390
391
                    $updatedElement = new ListElement(
392
                        new ListElementUuid($elementUuid),
393
                        $updatedElementBody
394
                    );
395
                    $body = $updatedElement->getBody();
396
397
                    $tx->hset(
398
                        $chunkNumber,
399
                        $elementUuid,
400
                        $body
401
                    );
402
403
                    break;
404
                }
405
            }
406
        });
407
    }
408
409
    /**
410
     * @param $listUuid
411
     * @param null $ttl
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $ttl is correct as it would always require null to be passed?
Loading history...
412
     *
413
     * @return mixed
414
     *
415
     * @throws ListDoesNotExistsException
416
     */
417
    public function updateTtl($listUuid, $ttl)
418
    {
419
        if (!$this->findListByUuid($listUuid)) {
420
            throw new ListDoesNotExistsException('List '.$listUuid.' does not exists in memory.');
421
        }
422
423
        // update ttl of all chunks
424
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
425
426
        $options = [
427
            'cas' => true,
428
            'watch' => $this->getArrayChunksKeys($listUuid, $numberOfChunks),
429
            'retry' => 3,
430
        ];
431
432
        // persist in a transaction
433
        $this->client->transaction($options, function ($tx) use ($numberOfChunks, $listUuid, $ttl) {
434
            for ($i = 1; $i <= $numberOfChunks; ++$i) {
435
                $tx->expire(
436
                    (string) $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i,
437
                    (int) $ttl
438
                );
439
            }
440
441
            // update ttl of headers array (if present)
442
            if ($this->getHeaders($listUuid)) {
443
                $tx->expire($listUuid.self::SEPARATOR.self::HEADERS, $ttl);
444
            }
445
446
            // update index
447
            $this->addOrUpdateListToIndex(
448
                $listUuid,
449
                $this->getCounter($listUuid),
450
                $this->getNumberOfChunks($listUuid),
451
                $this->getChunkSize($listUuid),
452
                $ttl
453
            );
454
        });
455
    }
456
}
457