Test Failed
Branch feature/v1_stable_fixes (e805e7)
by Diego
04:22
created

CacheableRepository::all()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 20
rs 9.8666
1
<?php
2
3
namespace Blackmine\Repository;
4
5
use Blackmine\Client\ClientInterface;
6
use Blackmine\Client\ClientOptions;
7
use Blackmine\Client\Generator\KeyGeneratorInterface;
8
use Blackmine\Exception\Api\AbstractApiException;
9
use Blackmine\Exception\InvalidModelException;
10
use Blackmine\Model\AbstractModel;
11
use Blackmine\Model\User\User;
12
use Doctrine\Common\Collections\ArrayCollection;
13
use Doctrine\Common\Collections\Collection;
14
use JsonException;
15
use Psr\Cache\InvalidArgumentException;
16
use Symfony\Contracts\Cache\CacheInterface;
17
use Symfony\Contracts\Cache\ItemInterface;
18
use Symfony\Contracts\Cache\TagAwareCacheInterface;
19
20
class CacheableRepository implements RepositoryInterface, SearchableRepositoryInterface
21
{
22
    use RepositoryTrait;
23
    use SearchableTrait;
24
25
    public function __construct(
26
        protected AbstractRepository $repository,
27
        protected CacheInterface | TagAwareCacheInterface $cache,
28
        protected KeyGeneratorInterface $generator,
29
        protected array $options = [
30
            ClientOptions::CLIENT_OPTION_REQUEST_HEADERS => []
31
        ]
32
    ) {
33
    }
34
35
    public function actingAs(string | User $user): self
36
    {
37
        if ($user instanceof User) {
38
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] =
39
                $user->getLogin();
40
        } else {
41
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] =
42
                $user;
43
        }
44
45
        return $this;
46
    }
47
48
    /**
49
     * @throws InvalidArgumentException
50
     */
51
    public function get(mixed $id): ?AbstractModel
52
    {
53
        $cache_key = $this->generator->generate($this->repository->getModelClass(), $id);
54
        return $this->cache->get($cache_key, function (ItemInterface $item) use ($id) {
55
            $model = $this->repository->get($id);
56
            if ($model) {
57
                $item->set($model);
58
                $item->expiresAfter($this->getCacheTTL());
59
                $item->tag([$model->getEntityName()]);
60
            }
61
62
            return $model;
63
        });
64
    }
65
66
    public function all(?string $endpoint = null): Collection
67
    {
68
        $api_endpoint = $endpoint ?? $this->getEndpoint() . "." . $this->getClient()->getFormat();
69
70
        $cache_key = $this->generator->generate(
71
            $api_endpoint,
72
            json_encode($this->getFetchRelations(), JSON_THROW_ON_ERROR)
73
        );
74
75
        return $this->cache->get($cache_key, function (ItemInterface $item) use ($api_endpoint) {
76
            $collection = $this->repository->all($api_endpoint);
77
            if (!$collection->isEmpty()) {
78
                $item->set($collection);
79
                $item->expiresAfter($this->getCacheTTL());
80
                if ($this->supportsTagging()) {
81
                    $item->tag([$collection->first()->getEntityName() . "_all"]);
82
                }
83
            }
84
85
            return $collection;
86
        });
87
    }
88
89
    /**
90
     * @throws InvalidArgumentException
91
     * @throws JsonException
92
     * @throws AbstractApiException
93
     * @throws InvalidModelException
94
     */
95
    public function create(AbstractModel $model): ?AbstractModel
96
    {
97
        $model = $this->repository->create($model);
98
        if ($model?->isCacheable()) {
99
            return $this->cacheModel($model);
100
        }
101
102
        return $model;
103
    }
104
105
    /**
106
     * @throws InvalidArgumentException
107
     * @throws JsonException
108
     * @throws AbstractApiException
109
     * @throws InvalidModelException
110
     */
111
    public function update(AbstractModel $model): ?AbstractModel
112
    {
113
        $model = $this->repository->update($model);
114
        if ($model?->isCacheable()) {
115
            return $this->cacheModel($model);
116
        }
117
118
        return $model;
119
    }
120
121
    /**
122
     * @throws InvalidArgumentException
123
     * @throws JsonException
124
     * @throws AbstractApiException
125
     * @throws InvalidModelException
126
     */
127
    public function delete(AbstractModel $model): void
128
    {
129
        $this->repository->delete($model);
130
        if ($model->isCacheable()) {
131
            $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
132
            $this->cache->delete($cache_key);
133
            if ($this->supportsTagging()) {
134
                $this->cache->invalidateTags([
135
                    $model->getEntityName() . "_search_results",
136
                    $model->getEntityName() . "_all"
137
                ]);
138
            }
139
        }
140
    }
141
142
    /**
143
     * @throws JsonException
144
     * @throws InvalidArgumentException
145
     */
146
    public function search(): ArrayCollection
147
    {
148
        $cache_key = $this->generator->generate(
149
            $this->repository->getEndpoint(),
150
            json_encode(static::$filter_params, JSON_THROW_ON_ERROR)
151
        );
152
153
        return $this->cache->get($cache_key, function (ItemInterface $item) {
154
            $search_results = $this->doSearch();
155
            if (!$search_results->isEmpty()) {
156
                $item->set($search_results);
157
                $item->expiresAfter($this->getCacheTTL());
158
                if ($this->supportsTagging()) {
159
                    $item->tag([$search_results->first()->getEntityName() . "_search_results"]);
160
                }
161
            }
162
163
            return $search_results;
164
        });
165
    }
166
167
    public function getClient(): ClientInterface
168
    {
169
        return $this->repository->getClient();
170
    }
171
172
    public function getAllowedFilters(): array
173
    {
174
        return $this->repository->getAllowedFilters();
175
    }
176
177
    public function getRelationClassMap(): array
178
    {
179
        return $this->repository->getRelationClassMap();
180
    }
181
182
    public function getEndpoint(): string
183
    {
184
        return $this->repository->getEndpoint();
185
    }
186
187
    public function constructEndpointUrl(string $endpoint, array $params): string
188
    {
189
        return $this->repository->constructEndpointUrl($endpoint, $params);
190
    }
191
192
    public function addRelationToFetch(string $relation): void
193
    {
194
        $this->repository->addRelationToFetch($relation);
195
    }
196
197
    public function getModelClass(): string
198
    {
199
        return $this->repository->getModelClass();
200
    }
201
202
    public function __call(string $method, array $args): mixed
203
    {
204
        if (method_exists($this->repository, $method)) {
205
            return call_user_func_array([$this->repository, $method], $args);
206
        }
207
208
        return null;
209
    }
210
211
    /**
212
     * @throws InvalidArgumentException
213
     */
214
    protected function cacheModel(AbstractModel $model): ?AbstractModel
215
    {
216
        $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
217
218
        $this->cache->delete($cache_key);
219
        return $this->cache->get($cache_key, function (ItemInterface $item) use ($model) {
220
            $item->set($model);
221
            $item->expiresAfter($this->getCacheTTL());
222
            if ($this->supportsTagging()) {
223
                $this->cache->invalidateTags([
224
                    $model->getEntityName() . "_search_results",
225
                    $model->getEntityName() . "_all"
226
                ]);
227
                $item->tag([$model->getEntityName()]);
228
            }
229
230
            return $model;
231
        });
232
    }
233
234
    protected function getCacheTTL(): int
235
    {
236
        return $this->options[ClientOptions::CLIENT_OPTIONS_CACHE_TTL] ?? ClientOptions::CACHE_DEFAULT_TTL;
237
    }
238
239
    protected function supportsTagging(): bool
240
    {
241
        return $this->cache instanceof TagAwareCacheInterface;
242
    }
243
244
    public static function getRelationClassFor(string $relation): ?string
245
    {
246
        return null;
247
    }
248
249
    public function getFetchRelations(): array
250
    {
251
        return $this->repository->getFetchRelations();
252
    }
253
}
254