Passed
Branch 001-basic (dcea18)
by Mr.
07:50
created

BookRepositoryRedis::load()   B

Complexity

Conditions 11
Paths 9

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11.6363

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 19
cts 23
cp 0.8261
crap 11.6363
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\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