ApcuRepository::updateTtl()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
nc 9
nop 2
dl 0
loc 40
rs 9.1928
c 0
b 0
f 0
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 ApcuRepository extends AbstractRepository implements ListRepositoryInterface
23
{
24
    /**
25
     * @param ListCollection $list
26
     * @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...
27
     * @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...
28
     *
29
     * @return mixed
30
     *
31
     * @throws ListAlreadyExistsException
32
     */
33
    public function create(ListCollection $list, $ttl = null, $chunkSize = null)
34
    {
35
        // check if list already exists in memory
36
        $listUuid = (string) $list->getUuid();
37
        if ($this->existsListInIndex($listUuid) && $this->exists($listUuid)) {
38
            throw new ListAlreadyExistsException('List '.$list->getUuid().' already exists in memory.');
39
        }
40
41
        if (!$chunkSize && !is_int($chunkSize)) {
0 ignored issues
show
introduced by
$chunkSize is of type null, thus it always evaluated to false.
Loading history...
introduced by
The condition is_int($chunkSize) is always false.
Loading history...
42
            $chunkSize = self::CHUNKSIZE;
43
        }
44
45
        // create arrayOfElements
46
        $arrayOfElements = [];
47
48
        /** @var ListElement $element */
49
        foreach ($list->getElements() as $element) {
50
            $arrayOfElements[(string) $element->getUuid()] = $element->getBody();
51
        }
52
53
        // persist in memory array in chunks
54
        $arrayChunks = array_chunk($arrayOfElements, $chunkSize, true);
55
        foreach ($arrayChunks as $chunkNumber => $item) {
56
            $arrayToPersist = [];
57
            foreach ($item as $key => $element) {
58
                $arrayToPersist[$key] = $element;
59
            }
60
61
            apcu_store(
62
                (string) $list->getUuid().self::SEPARATOR.'chunk-'.($chunkNumber + 1),
63
                $arrayToPersist,
64
                $ttl
65
            );
66
        }
67
68
        // set headers
69
        if ($list->getHeaders()) {
70
            apcu_store(
71
                (string) $list->getUuid().self::SEPARATOR.self::HEADERS,
72
                $list->getHeaders(),
73
                $ttl
74
            );
75
        }
76
77
        // add list to index
78
        $this->addOrUpdateListToIndex(
79
            (string) $listUuid,
80
            (int) count($list->getElements()),
81
            (int) count($arrayChunks),
82
            (int) $chunkSize,
83
            $ttl
84
        );
85
    }
86
87
    /**
88
     * @param $listUuid
89
     * @param $elementUuid
90
     *
91
     * @return mixed
92
     */
93
    public function deleteElement($listUuid, $elementUuid)
94
    {
95
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
96
        $chunkSize = $this->getChunkSize($listUuid);
97
98
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
99
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
100
            $chunk = apcu_fetch($chunkNumber);
101
102
            if (array_key_exists($elementUuid, $chunk)) {
103
                // delete elements from chunk
104
                unset($chunk[(string) $elementUuid]);
105
                apcu_delete($chunkNumber);
106
                apcu_store($chunkNumber, $chunk);
107
108
                // update list index
109
                $prevIndex = $this->getIndex($listUuid);
110
                $this->addOrUpdateListToIndex(
111
                    $listUuid,
112
                    ($prevIndex['size'] - 1),
113
                    $numberOfChunks,
114
                    $chunkSize,
115
                    $prevIndex['ttl']
116
                );
117
118
                // delete headers if counter = 0
119
                $counter = $this->getCounter($listUuid);
120
                $headersKey = $listUuid.self::SEPARATOR.self::HEADERS;
121
122
                if ($counter === 0) {
123
                    apcu_delete($headersKey);
124
                }
125
126
                break;
127
            }
128
        }
129
    }
130
131
    /**
132
     * @param $listUuid
133
     *
134
     * @return bool
135
     */
136
    public function exists($listUuid)
137
    {
138
        $listFirstChunk = apcu_fetch($listUuid.self::SEPARATOR.self::CHUNK.'-1');
139
140
        return isset($listFirstChunk);
141
    }
142
143
    /**
144
     * @param $listUuid
145
     *
146
     * @return mixed
147
     */
148
    public function findListByUuid($listUuid)
149
    {
150
        $collection = (apcu_fetch($listUuid.self::SEPARATOR.self::CHUNK.'-1')) ?: [];
151
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
152
153
        for ($i = 2; $i <= $numberOfChunks; ++$i) {
154
            $collection = (array) array_merge($collection, apcu_fetch($listUuid.self::SEPARATOR.self::CHUNK.'-'.$i));
155
        }
156
157
        return (array) array_map('unserialize', $collection);
158
    }
159
160
    /**
161
     * @return mixed
162
     */
163
    public function flush()
164
    {
165
        apcu_clear_cache();
166
    }
167
168
    /**
169
     * @param $listUuid
170
     *
171
     * @return array
172
     */
173
    public function getHeaders($listUuid)
174
    {
175
        return apcu_fetch($listUuid.self::SEPARATOR.'headers');
176
    }
177
178
    /**
179
     * @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...
180
     *
181
     * @return mixed
182
     */
183
    public function getIndex($listUuid = null)
184
    {
185
        $indexKey = ListRepositoryInterface::INDEX;
186
        $index = apcu_fetch($indexKey);
187
        $this->removeExpiredListsFromIndex($index);
188
189
        if ($listUuid) {
0 ignored issues
show
introduced by
$listUuid is of type null, thus it always evaluated to false.
Loading history...
190
            return (isset($index[(string) $listUuid])) ? unserialize($index[(string) $listUuid]) : null;
191
        }
192
193
        return ($index) ? array_map('unserialize', $index) : [];
194
    }
195
196
    /**
197
     * @param $listUuid
198
     * @param $size
199
     * @param $numberOfChunks
200
     * @param $chunkSize
201
     * @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...
202
     */
203
    private function addOrUpdateListToIndex($listUuid, $size, $numberOfChunks, $chunkSize, $ttl = null)
204
    {
205
        $indexKey = ListRepositoryInterface::INDEX;
206
        $indexArray = serialize([
207
            'uuid' => $listUuid,
208
            'created_on' => new \DateTimeImmutable(),
209
            'size' => $size,
210
            'chunks' => $numberOfChunks,
211
            'chunk-size' => $chunkSize,
212
            'headers' => $this->getHeaders($listUuid),
213
            'ttl' => $ttl,
214
        ]);
215
216
        $indexArrayToUpdate[(string) $listUuid] = $indexArray;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$indexArrayToUpdate was never initialized. Although not strictly required by PHP, it is generally a good practice to add $indexArrayToUpdate = array(); before regardless.
Loading history...
217
        $index = apcu_fetch((string) $indexKey);
218
219
        if ($this->existsListInIndex($listUuid) or is_array($index)) {
220
            $index[$listUuid] = $indexArray;
221
            $indexArrayToUpdate = $index;
222
            apcu_delete($indexKey);
223
        }
224
225
        apcu_store((string) $indexKey, $indexArrayToUpdate);
226
227
        if ($size === 0) {
228
            $this->removeListFromIndex($listUuid);
229
        }
230
    }
231
232
    /**
233
     * @return array
234
     */
235
    public function getStatistics()
236
    {
237
        return (array) apcu_cache_info();
238
    }
239
240
    /**
241
     * @param $listUuid
242
     * @param ListElement $listElement
243
     *
244
     * @throws ListElementNotConsistentException
245
     *
246
     * @return mixed
247
     */
248
    public function pushElement($listUuid, ListElement $listElement)
249
    {
250
        $elementUuid = $listElement->getUuid();
251
        $body = $listElement->getBody();
252
253
        if (!ListElementConsistencyChecker::isConsistent($listElement, $this->findListByUuid($listUuid))) {
254
            throw new ListElementNotConsistentException('Element '.(string) $listElement->getUuid().' is not consistent with list data.');
255
        }
256
257
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
258
        $chunkSize = $this->getChunkSize($listUuid);
259
        $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
260
        $ttl = ($this->getTtl($listUuid) > 0) ? $this->getTtl($listUuid) : null;
261
262
        if ($chunkSize - count(apcu_fetch($chunkNumber)) === 0) {
263
            ++$numberOfChunks;
264
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$numberOfChunks;
265
        }
266
267
        $chunkValues = apcu_fetch($chunkNumber);
268
        $chunkValues[(string) $elementUuid] = (string) $body;
269
270
        apcu_delete($chunkNumber);
271
        apcu_store(
272
            $chunkNumber,
273
            $chunkValues,
274
            $ttl
275
        );
276
277
        // update list index
278
        $prevIndex = $this->getIndex($listUuid);
279
        $this->addOrUpdateListToIndex(
280
            $listUuid,
281
            ($prevIndex['size'] + 1),
282
            $numberOfChunks,
283
            $chunkSize,
284
            $ttl
0 ignored issues
show
Bug introduced by
It seems like $ttl can also be of type integer; however, parameter $ttl of InMemoryList\Infrastruct...ddOrUpdateListToIndex() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
            /** @scrutinizer ignore-type */ $ttl
Loading history...
285
        );
286
    }
287
288
    /**
289
     * @param $listUuid
290
     *
291
     * @return mixed
292
     */
293
    public function removeListFromIndex($listUuid)
294
    {
295
        $indexKey = ListRepositoryInterface::INDEX;
296
297
        $index = apcu_fetch($indexKey);
298
        unset($index[(string) $listUuid]);
299
300
        apcu_delete($indexKey);
301
        apcu_store($indexKey, $index);
302
    }
303
304
    /**
305
     * @param $listUuid
306
     * @param $elementUuid
307
     * @param array $data
308
     *
309
     * @throws ListElementNotConsistentException
310
     *
311
     * @return mixed
312
     */
313
    public function updateElement($listUuid, $elementUuid, $data)
314
    {
315
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
316
        $ttl = ($this->getTtl($listUuid) > 0) ? $this->getTtl($listUuid) : null;
317
318
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
319
            $chunkNumber = $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
320
            $chunk = apcu_fetch($chunkNumber);
321
322
            if (array_key_exists($elementUuid, $chunk)) {
323
                $listElement = $this->findElement(
324
                    (string) $listUuid,
325
                    (string) $elementUuid
326
                );
327
328
                $updatedElementBody = $this->updateListElementBody($listElement, $data);
329
                if (!ListElementConsistencyChecker::isConsistent($updatedElementBody, $this->findListByUuid($listUuid))) {
330
                    throw new ListElementNotConsistentException('Element '.(string) $elementUuid.' is not consistent with list data.');
331
                }
332
333
                $arrayOfElements = apcu_fetch($listUuid);
334
                $updatedElement = new ListElement(
335
                    new ListElementUuid($elementUuid),
336
                    $updatedElementBody
337
                );
338
                $body = $updatedElement->getBody();
339
                $arrayOfElements[(string) $elementUuid] = $body;
340
341
                apcu_delete($chunkNumber);
342
                apcu_store(
343
                    (string) $chunkNumber,
344
                    $arrayOfElements,
345
                    $ttl
346
                );
347
348
                break;
349
            }
350
        }
351
    }
352
353
    /**
354
     * @param $listUuid
355
     * @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...
356
     *
357
     * @return mixed
358
     *
359
     * @throws ListDoesNotExistsException
360
     */
361
    public function updateTtl($listUuid, $ttl)
362
    {
363
        if (!$this->findListByUuid($listUuid)) {
364
            throw new ListDoesNotExistsException('List '.$listUuid.' does not exists in memory.');
365
        }
366
367
        $ttl = ($ttl > 0) ? $ttl : null;
368
369
        // update ttl of all chunks
370
        $numberOfChunks = $this->getNumberOfChunks($listUuid);
371
        for ($i = 1; $i <= $numberOfChunks; ++$i) {
372
            $chunkNumber = (string) $listUuid.self::SEPARATOR.self::CHUNK.'-'.$i;
373
            $storedListInChunk = apcu_fetch($chunkNumber);
374
            apcu_delete((string) $chunkNumber);
375
            apcu_store(
376
                (string) $chunkNumber,
377
                $storedListInChunk,
378
                $ttl
379
            );
380
        }
381
382
        // update ttl of headers array (if present)
383
        if ($this->getHeaders($listUuid)) {
384
            $headers = (string) $listUuid.self::SEPARATOR.self::HEADERS;
385
            $storedHeaders = apcu_fetch($headers);
386
            apcu_delete((string) $headers);
387
            apcu_store(
388
                (string) $headers,
389
                $storedHeaders,
390
                $ttl
391
            );
392
        }
393
394
        // update index
395
        $this->addOrUpdateListToIndex(
396
            (string) $listUuid,
397
            $this->getCounter((string) $listUuid),
398
            $this->getNumberOfChunks((string) $listUuid),
399
            $this->getChunkSize((string) $listUuid),
400
            $ttl
401
        );
402
    }
403
}
404