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

AuthorRepositoryRedis   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Test Coverage

Coverage 90.77%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 186
ccs 59
cts 65
cp 0.9077
rs 10
wmc 28

8 Methods

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

86
                    /** @scrutinizer ignore-call */ 
87
                    $author = $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...
87
                    // And after that we can warm our temporary-storage.
88 5
                    if ($author) {
89
                        try {
90 2
                            $this->saveInternal($author);
91
                        } catch (\LibraryCatalog\Service\Repository\Exception $e) {
92
                            // @todo Log
93
                            // Return result as we can live with parent storage only.
94
                        }
95
                    }
96 5
                    return $author;
97 5
                }
98
            );
99
        }
100
101 10
        return $author;
102
    }
103
104
    /**
105
     * @param string $name
106
     * @param string $birthdate
107
     * @return Author|null
108
     */
109 1
    public function loadByNameBirthdate(string $name, string $birthdate): ?Author
110
    {
111 1
        $res = null;
112 1
        if ($this->parentRepository) {
113 1
            $res = $this->parentRepository->loadByNameBirthdate($name, $birthdate);
114
        }
115 1
        return $res;
116
    }
117
118
    /**
119
     * @param Author $author
120
     * @throws Serializer\HydrateException
121
     * @throws \LibraryCatalog\Service\Repository\Exception
122
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
123
     */
124 4
    public function save(Author $author): void
125
    {
126
        // First save to parent repository (DB).
127 4
        if ($this->parentRepository) {
128 4
            $this->parentRepository->save($author);
129
        }
130 4
        $this->saveInternal($author);
131 4
    }
132
133
    /**
134
     * @param object $object
135
     * @throws Serializer\HydrateException
136
     * @throws \LibraryCatalog\Service\Repository\Exception
137
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
138
     */
139 3
    public function warm(object $object): void
140
    {
141 3
        if ($object instanceof Author) {
142 3
            $this->saveInternal($object);
143
        }
144 3
        if ($this->parentRepository instanceof WarmRepositoryInterface) {
145
            $this->parentRepository->warm($object);
0 ignored issues
show
Bug introduced by
The method warm() does not exist on LibraryCatalog\Service\R...thorRepositoryInterface. It seems like you code against a sub-type of LibraryCatalog\Service\R...thorRepositoryInterface such as LibraryCatalog\Infrastru...e\AuthorRepositoryRedis. ( Ignorable by Annotation )

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

145
            $this->parentRepository->/** @scrutinizer ignore-call */ 
146
                                     warm($object);
Loading history...
146
        }
147 3
    }
148
149
    /**
150
     * @param mixed $id
151
     * @throws \LibraryCatalog\Service\Repository\Exception
152
     */
153 2
    public function reset($id): void
154
    {
155 2
        if ($id != '') {
156 2
            $this->client->del([
157 2
                $this->formatKey($id, true),
158 2
                $this->formatKey($id, false),
159
            ]);
160 2
            if ($this->parentRepository instanceof WarmRepositoryInterface) {
161
                $this->parentRepository->reset($id);
0 ignored issues
show
Bug introduced by
The method reset() does not exist on LibraryCatalog\Service\R...thorRepositoryInterface. It seems like you code against a sub-type of LibraryCatalog\Service\R...thorRepositoryInterface such as LibraryCatalog\Infrastru...e\AuthorRepositoryRedis. ( Ignorable by Annotation )

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

161
                $this->parentRepository->/** @scrutinizer ignore-call */ 
162
                                         reset($id);
Loading history...
162
            }
163
        }
164 2
    }
165
166
    /**
167
     * Saves only in current repository.
168
     *
169
     * @param Author $author
170
     * @return void
171
     * @throws Serializer\HydrateException
172
     * @throws \LibraryCatalog\Transformer\Encoder\Exception
173
     * @throws \LibraryCatalog\Service\Repository\Exception
174
     */
175 8
    public function saveInternal(Author $author): void
176
    {
177 8
        if ($author->id) {
178
            if (
179 8
                !$this->client->set(
180 8
                    $this->formatKey($author->id, $author->areBooksLoaded()),
181 8
                    $this->serializer->serialize($author)
182
                )
183
            ) {
184
                throw new \LibraryCatalog\Service\Repository\Exception("Can not save Author to the Redis");
185
            }
186
        }
187 8
    }
188
189
    /**
190
     * @param mixed $id
191
     * @param bool $withIncludes
192
     * @return string
193
     */
194 13
    protected function formatKey($id, bool $withIncludes): string
195
    {
196 13
        $res = $this->keyPrefix . 'author-';
197 13
        if ($withIncludes) {
198 6
            $res .= 'wi-';
199
        }
200 13
        return $res . $id;
201
    }
202
}
203