Passed
Branch 001-basic (dcea18)
by Mr.
02:13
created

AuthorRepositoryRedis::load()   B

Complexity

Conditions 11
Paths 9

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 11.2682

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 22
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 44
ccs 20
cts 23
cp 0.8696
crap 11.2682
rs 7.3166

How to fix   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
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