Passed
Branch main (4cb13d)
by Mr.
04:54 queued 02:43
created

BookRepositoryRedis   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Test Coverage

Coverage 77.42%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 57
c 1
b 0
f 0
dl 0
loc 174
ccs 48
cts 62
cp 0.7742
rs 10
wmc 27

8 Methods

Rating   Name   Duplication   Size   Complexity  
A saveInternal() 0 10 3
A __construct() 0 10 2
A formatKey() 0 7 2
A reset() 0 9 3
A save() 0 7 2
A loadByAuthorId() 0 4 1
A warm() 0 7 3
B load() 0 44 11
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LibraryCatalog\Infrastructure\Persistence;
6
7
use LibraryCatalog\Entity\Book;
8
use LibraryCatalog\Service\Repository\BookRepositoryInterface;
9
use LibraryCatalog\Service\Repository\RedisTrait;
10
use LibraryCatalog\Service\Repository\SerializerTrait;
11
use LibraryCatalog\Service\Repository\WarmRepositoryInterface;
12
use LibraryCatalog\Transformer\Serializer;
13
use Predis\Client;
14
15
class BookRepositoryRedis implements BookRepositoryInterface, WarmRepositoryInterface
16
{
17
    use SerializerTrait;
18
    use RedisTrait;
19
20
    protected const LOCK_TTL = 5;
21
22
    /** @var BookRepositoryInterface|null */
23
    protected ?BookRepositoryInterface $parentRepository;
24
    /** @var Client */
25
    protected Client $client;
26
    /** @var string */
27
    protected string $keyPrefix;
28
29 1
    public function __construct(
30
        ?BookRepositoryInterface $parentRepository,
31
        Serializer $serializer,
32
        Client $client,
33
        string $versionPrefix
34
    ) {
35 1
        $this->parentRepository = $parentRepository;
36 1
        $this->serializer = $serializer;
37 1
        $this->client = $client;
38 1
        $this->keyPrefix = $versionPrefix === '' ? '' : $versionPrefix . '-';
39 1
    }
40
41
    /**
42
     * @param mixed $id
43
     * @param bool $withAuthor
44
     * @return Book|null
45
     * @throws Serializer\Exception
46
     * @throws Serializer\HydrateException
47
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
48
     * @throws \Throwable
49
     */
50 4
    public function load($id, bool $withAuthor = false): ?Book
51
    {
52 4
        if ($id == '') {
53
            return null;
54
        }
55
56 4
        $data = $this->client->get($this->formatKey($id, $withAuthor));
57 4
        if ($withAuthor && !$data) {
58
            // Try to load from cache but without author.
59 4
            $data = $this->client->get($this->formatKey($id, false));
60 4
            $wasReloaded = true;
61
        }
62
63 4
        $book = $this->deserialize($data, Book::class);
64
65 4
        if ($withAuthor && !isset($wasReloaded) && $book) {
66
            // It means we got books from cache also, should mark in the entity.
67
            $book->setAuthor($book->author);
68
        }
69
70
        // We use parent Repository (usually DB) if data is not present or has invalidated by prefix.
71 4
        if (!$book && $this->parentRepository instanceof BookRepositoryInterface) {
72
            // Implement lock to make other request waiting for cache warming.
73 2
            $book = $this->transaction(
74 2
                $this->client,
75 2
                $this->keyPrefix . 'lock-book-' . $id,
76 2
                static::LOCK_TTL,
77 2
                function () use ($id) {
78 2
                    $book = $this->parentRepository->load($id);
0 ignored issues
show
Bug introduced by
The method load() does not exist on null. ( Ignorable by Annotation )

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

78
                    /** @scrutinizer ignore-call */ 
79
                    $book = $this->parentRepository->load($id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
79
                    // And after that we can warm our temporary-storage.
80 2
                    if ($book) {
81
                        try {
82
                            $this->saveInternal($book);
83
                        } catch (\LibraryCatalog\Service\Repository\Exception $e) {
84
                            // @todo Log
85
                            // Return result as we can live with parent storage only.
86
                        }
87
                    }
88 2
                    return $book;
89 2
                }
90
            );
91
        }
92
93 4
        return $book;
94
    }
95
96
    /**
97
     * @param mixed $authorId
98
     * @return Book[]
99
     */
100 3
    public function loadByAuthorId($authorId): array
101
    {
102
        // As a cache repository it does not search.
103 3
        return $this->parentRepository->loadByAuthorId($authorId);
104
    }
105
106
    /**
107
     * @param Book $book
108
     * @throws Serializer\HydrateException
109
     * @throws \LibraryCatalog\Service\Repository\Exception
110
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
111
     */
112 2
    public function save(Book $book): void
113
    {
114
        // First save to parent repository (DB).
115 2
        if ($this->parentRepository) {
116 2
            $this->parentRepository->save($book);
117
        }
118 2
        $this->saveInternal($book);
119 2
    }
120
121
    /**
122
     * @param object $object
123
     * @throws Serializer\HydrateException
124
     * @throws \LibraryCatalog\Service\Repository\Exception
125
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
126
     */
127 2
    public function warm(object $object): void
128
    {
129 2
        if ($object instanceof Book) {
130 2
            $this->saveInternal($object);
131
        }
132 2
        if ($this->parentRepository instanceof WarmRepositoryInterface) {
133
            $this->parentRepository->warm($object);
0 ignored issues
show
Bug introduced by
The method warm() does not exist on LibraryCatalog\Service\R...BookRepositoryInterface. It seems like you code against a sub-type of LibraryCatalog\Service\R...BookRepositoryInterface such as LibraryCatalog\Infrastru...nce\BookRepositoryRedis. ( Ignorable by Annotation )

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

133
            $this->parentRepository->/** @scrutinizer ignore-call */ 
134
                                     warm($object);
Loading history...
134
        }
135 2
    }
136
137
    /**
138
     * @param mixed $id
139
     * @throws \LibraryCatalog\Service\Repository\Exception
140
     */
141
    public function reset($id): void
142
    {
143
        if ($id != '') {
144
            $this->client->del([
145
                $this->formatKey($id, true),
146
                $this->formatKey($id, false),
147
            ]);
148
            if ($this->parentRepository instanceof WarmRepositoryInterface) {
149
                $this->parentRepository->reset($id);
0 ignored issues
show
Bug introduced by
The method reset() does not exist on LibraryCatalog\Service\R...BookRepositoryInterface. It seems like you code against a sub-type of LibraryCatalog\Service\R...BookRepositoryInterface such as LibraryCatalog\Infrastru...nce\BookRepositoryRedis. ( Ignorable by Annotation )

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

149
                $this->parentRepository->/** @scrutinizer ignore-call */ 
150
                                         reset($id);
Loading history...
150
            }
151
        }
152
    }
153
154
    /**
155
     * Saves only in current repository.
156
     *
157
     * @param Book $book
158
     * @return void
159
     * @throws Serializer\HydrateException
160
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
161
     * @throws \LibraryCatalog\Service\Repository\Exception
162
     */
163 4
    protected function saveInternal(Book $book): void
164
    {
165 4
        if ($book->id) {
166
            if (
167 4
                !$this->client->set(
168 4
                    $this->formatKey($book->id, $book->isAuthorLoaded()),
169 4
                    $this->serializer->serialize($book)
170
                )
171
            ) {
172
                throw new \LibraryCatalog\Service\Repository\Exception("Can not save Book to the Redis");
173
            }
174
        }
175 4
    }
176
177
    /**
178
     * @param mixed $id
179
     * @param bool $withIncludes
180
     * @return string
181
     */
182 6
    protected function formatKey($id, bool $withIncludes): string
183
    {
184 6
        $res = $this->keyPrefix . 'book-';
185 6
        if ($withIncludes) {
186 4
            $res .= 'wi-';
187
        }
188 6
        return $res . $id;
189
    }
190
}
191