MemcachedRepository::create()   B
last analyzed

Complexity

Conditions 9
Paths 25

Size

Total Lines 51
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 28
nc 25
nop 3
dl 0
loc 51
rs 8.0555
c 0
b 0
f 0

How to fix   Long Method   

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
22
class MemcachedRepository extends AbstractRepository implements ListRepositoryInterface
23
{
24
    /**
25
     * @var \Memcached
26
     */
27
    private $memcached;
28
29
    /**
30
     * MemcachedRepository constructor.
31
     *
32
     * @param \Memcached $memcached
33
     */
34
    public function __construct(\Memcached $memcached)
35
    {
36
        $this->memcached = $memcached;
37
    }
38
39
    /**
40
     * @param ListCollection $list
41
     * @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...
42
     * @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...
43
     *
44
     * @return mixed
45
     *
46
     * @throws ListAlreadyExistsException
47
     */
48
    public function create(ListCollection $list, $ttl = null, $chunkSize = null)
49
    {
50
        // check if list already exists in memory
51
        $listUuid = (string) $list->getUuid();
52
        if ($this->existsListInIndex($listUuid) && $this->exists($listUuid)) {
53
            throw new ListAlreadyExistsException('List '.$list->getUuid().' already exists in memory.');
54
        }
55
56
        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...
57
            $chunkSize = self::CHUNKSIZE;
58
        }
59
60
        // create arrayOfElements
61
        $arrayOfElements = [];
62
63
        /** @var ListElement $element */
64
        foreach ($list->getElements() as $element) {
65
            $arrayOfElements[(string) $element->getUuid()] = $element->getBody();
66
        }
67
68
        // persist in memory array in chunks
69
        $arrayChunks = array_chunk($arrayOfElements, $chunkSize, true);
70
        foreach ($arrayChunks as $chunkNumber => $item) {
71
            $arrayToPersist = [];
72
            foreach ($item as $key => $element) {
73
                $arrayToPersist[$key] = $element;
74
            }
75
76
            $this->memcached->set(
77
                (string) $list->getUuid().self::SEPARATOR.'chunk-'.($chunkNumber + 1),
78
                $arrayToPersist,
79
                $ttl
80
            );
81
        }
82
83
        // set headers
84
        if ($list->getHeaders()) {
85
            $this->memcached->set(
86
                (string) $list->getUuid().self::SEPARATOR.self::HEADERS,
87
                $list->getHeaders(),
88
                $ttl
89
            );
90
        }
91
92
        // add list to index
93
        $this->addOrUpdateListToIndex(
94
            $listUuid,
95
            (int) count($list->getElements()),
96
            (int) count($arrayChunks),
97
            (int) $chunkSize,
98
            $ttl
99
        );
100
    }
101
102
    /**
103
     * @param $listUuid
104
     * @param $elementUuid
105
     *
106
     * @return mixed
107
     */
108
    public function deleteElement($listUuid, $elementUuid)
109
    {
110
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
111
        $chunkSize = $this->getChunkSize($listUuid);
112
113
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
114
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
115
            $chunk = $this->memcached->get($chunkNumber);
116
117
            if (array_key_exists($elementUuid, $chunk)) {
118
                // delete elements from chunk
119
                unset($chunk[(string) $elementUuid]);
120
                $this->memcached->replace($chunkNumber, $chunk);
121
122
                // update list index
123
                $prevIndex = $this->getIndex($listUuid);
124
                $this->addOrUpdateListToIndex(
125
                    $listUuid,
126
                    ($prevIndex['size'] - 1),
127
                    $numberOfChunks,
128
                    $chunkSize,
129
                    $prevIndex['ttl']
130
                );
131
132
                // delete headers if counter = 0
133
                $headersKey = $listUuid.self::SEPARATOR.self::HEADERS;
134
135
                if ($this->getCounter($listUuid) === 0) {
136
                    $this->memcached->delete($headersKey);
137
                }
138
139
                break;
140
            }
141
        }
142
    }
143
144
    /**
145
     * @param $listUuid
146
     *
147
     * @return bool
148
     */
149
    public function exists($listUuid)
150
    {
151
        $listFirstChunk = $this->memcached->get($listUuid.self::SEPARATOR.self::CHUNK.'-1');
152
153
        if (false === $listFirstChunk) {
154
            return false;
155
        }
156
157
        return true;
158
    }
159
160
    /**
161
     * @param $listUuid
162
     *
163
     * @return mixed
164
     */
165
    public function findListByUuid($listUuid)
166
    {
167
        $collection = ($this->memcached->get($listUuid.self::SEPARATOR.self::CHUNK.'-1')) ?: [];
168
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
169
170
        for ($i = 2; $i <= $numberOfChunks; ++$i) {
171
            $collection = (array) array_merge($collection, $this->memcached->get($listUuid.self::SEPARATOR.self::CHUNK.'-'.$i));
172
        }
173
174
        return (array) array_map('unserialize', $collection);
175
    }
176
177
    /**
178
     * @return mixed
179
     */
180
    public function flush()
181
    {
182
        $this->memcached->flush();
183
    }
184
185
    /**
186
     * @param $listUuid
187
     *
188
     * @return mixed
189
     */
190
    public function getHeaders($listUuid)
191
    {
192
        return $this->memcached->get($listUuid.self::SEPARATOR.self::HEADERS);
193
    }
194
195
    /**
196
     * @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...
197
     *
198
     * @return mixed
199
     */
200
    public function getIndex($listUuid = null)
201
    {
202
        $indexKey = ListRepositoryInterface::INDEX;
203
        $index = $this->memcached->get($indexKey);
204
        $this->removeExpiredListsFromIndex($index);
205
206
        if ($listUuid) {
0 ignored issues
show
introduced by
$listUuid is of type null, thus it always evaluated to false.
Loading history...
207
            return (isset($index[(string) $listUuid])) ? unserialize($index[(string) $listUuid]) : null;
208
        }
209
210
        return ($index) ? array_map('unserialize', $index) : [];
211
    }
212
213
    /**
214
     * @param $listUuid
215
     * @param $size
216
     * @param $numberOfChunks
217
     * @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...
218
     */
219
    private function addOrUpdateListToIndex($listUuid, $size, $numberOfChunks, $chunkSize, $ttl = null)
220
    {
221
        $indexKey = ListRepositoryInterface::INDEX;
222
        $indexArrayToUpdate = ($this->memcached->get($indexKey)) ?: [];
223
224
        $element = serialize([
225
            'uuid' => $listUuid,
226
            'created_on' => new \DateTimeImmutable(),
227
            'size' => $size,
228
            'chunks' => $numberOfChunks,
229
            'chunk-size' => $chunkSize,
230
            'headers' => $this->getHeaders($listUuid),
231
            'ttl' => $ttl,
232
        ]);
233
234
        $indexArrayToUpdate[(string) $listUuid] = $element;
235
236
        ($this->existsListInIndex($listUuid)) ? $this->memcached->replace($indexKey, $indexArrayToUpdate) : $this->memcached->set($indexKey, $indexArrayToUpdate);
237
238
        if ($size === 0) {
239
            $this->removeListFromIndex($listUuid);
240
        }
241
    }
242
243
    /**
244
     * @return array
245
     */
246
    public function getStatistics()
247
    {
248
        return $this->memcached->getStats();
249
    }
250
251
    /**
252
     * @param $listUuid
253
     * @param ListElement $listElement
254
     *
255
     * @throws ListElementNotConsistentException
256
     *
257
     * @return mixed
258
     */
259
    public function pushElement($listUuid, ListElement $listElement)
260
    {
261
        $elementUuid = $listElement->getUuid();
262
        $body = $listElement->getBody();
263
264
        if (!ListElementConsistencyChecker::isConsistent($listElement, $this->findListByUuid($listUuid))) {
265
            throw new ListElementNotConsistentException('Element '.(string) $listElement->getUuid().' is not consistent with list data.');
266
        }
267
268
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
269
        $chunkSize = $this->getChunkSize($listUuid);
270
        $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
271
272
        if ($chunkSize - count($this->memcached->get($chunkNumber)) === 0) {
273
            ++$numberOfChunks;
274
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
275
        }
276
277
        $chunkValues = $this->memcached->get($chunkNumber);
278
        $chunkValues[(string) $elementUuid] = (string) $body;
279
280
        $this->memcached->set(
281
            (string) $chunkNumber,
282
            $chunkValues,
283
            $this->getTtl($listUuid)
284
        );
285
286
        // update list index
287
        $prevIndex = $this->getIndex($listUuid);
288
        $this->addOrUpdateListToIndex(
289
            $listUuid,
290
            ($prevIndex['size'] + 1),
291
            $numberOfChunks,
292
            $chunkSize,
293
            $this->getTtl($listUuid)
294
        );
295
    }
296
297
    /**
298
     * @param $listUuid
299
     *
300
     * @return mixed
301
     */
302
    public function removeListFromIndex($listUuid)
303
    {
304
        $index = $this->memcached->get(ListRepositoryInterface::INDEX);
305
306
        unset($index[(string) $listUuid]);
307
        $this->memcached->replace(ListRepositoryInterface::INDEX, $index);
308
    }
309
310
    /**
311
     * @param $listUuid
312
     * @param $elementUuid
313
     * @param array $data
314
     *
315
     * @throws ListElementNotConsistentException
316
     *
317
     * @return mixed
318
     */
319
    public function updateElement($listUuid, $elementUuid, $data)
320
    {
321
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
322
        $ttl = ($this->getTtl($listUuid) > 0) ? $this->getTtl($listUuid) : null;
323
324
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
325
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
326
            $chunk = $this->memcached->get($chunkNumber);
327
328
            if (array_key_exists($elementUuid, $chunk)) {
329
                $listElement = $this->findElement(
330
                    (string) $listUuid,
331
                    (string) $elementUuid
332
                );
333
334
                $updatedElementBody = $this->updateListElementBody($listElement, $data);
335
                if (!ListElementConsistencyChecker::isConsistent($updatedElementBody, $this->findListByUuid($listUuid))) {
336
                    throw new ListElementNotConsistentException('Element '.(string) $elementUuid.' is not consistent with list data.');
337
                }
338
339
                $arrayOfElements = $this->memcached->get($listUuid);
340
                $updatedElement = new ListElement(
341
                    new ListElementUuid($elementUuid),
342
                    $updatedElementBody
343
                );
344
                $body = $updatedElement->getBody();
345
                $arrayOfElements[(string) $elementUuid] = $body;
346
347
                $this->memcached->replace(
348
                    (string) $chunkNumber,
349
                    $arrayOfElements,
350
                    $ttl
351
                );
352
353
                break;
354
            }
355
        }
356
    }
357
358
    /**
359
     * @param $listUuid
360
     * @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...
361
     *
362
     * @return mixed
363
     *
364
     * @throws ListDoesNotExistsException
365
     */
366
    public function updateTtl($listUuid, $ttl)
367
    {
368
        if (!$this->findListByUuid($listUuid)) {
369
            throw new ListDoesNotExistsException('List '.$listUuid.' does not exists in memory.');
370
        }
371
372
        // update ttl of all chunks
373
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
374
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
375
            $this->memcached->touch(
376
                (string) $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i,
377
                (int) $ttl
378
            );
379
        }
380
381
        // update ttl of headers array (if present)
382
        if ($this->getHeaders($listUuid)) {
383
            $this->memcached->touch(
384
                (string) $listUuid.self::SEPARATOR.self::HEADERS,
385
                (int) $ttl
386
            );
387
        }
388
389
        // update index
390
        $this->addOrUpdateListToIndex(
391
            $listUuid,
392
            $this->getCounter($listUuid),
393
            $this->getNumberOfChunks($listUuid),
394
            $this->getChunkSize($listUuid),
395
            $ttl
396
        );
397
    }
398
}
399