Passed
Push — main ( d581be...e16750 )
by Diego
02:53
created

CacheableRepository::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 7
rs 10
c 1
b 0
f 0
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 JsonException;
14
use Psr\Cache\InvalidArgumentException;
15
use Symfony\Contracts\Cache\CacheInterface;
16
use Symfony\Contracts\Cache\ItemInterface;
17
use Symfony\Contracts\Cache\TagAwareCacheInterface;
18
19
/**
20
 * @method ArrayCollection all(?string $endpoint = null)
21
 */
22
class CacheableRepository implements RepositoryInterface
23
{
24
    use RepositoryTrait;
25
    use SearchableTrait;
26
27
    public function __construct(
28
        protected AbstractRepository $repository,
29
        protected CacheInterface|TagAwareCacheInterface $cache,
30
        protected KeyGeneratorInterface $generator,
31
        protected array $options = [
32
            ClientOptions::CLIENT_OPTION_REQUEST_HEADERS => []
33
        ]
34
    ) {
35
    }
36
37
    public function actingAs(string | User $user): self
38
    {
39
        if ($user instanceof User) {
40
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] = $user->getLogin();
41
        } else {
42
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] = $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
    /**
67
     * @throws InvalidArgumentException
68
     * @throws JsonException
69
     * @throws AbstractApiException
70
     * @throws InvalidModelException
71
     */
72
    public function create(AbstractModel $model): ?AbstractModel
73
    {
74
        $model = $this->repository->create($model);
75
        if ($model?->isCacheable()) {
76
            return $this->cacheModel($model);
77
        }
78
79
        return $model;
80
    }
81
82
    /**
83
     * @throws InvalidArgumentException
84
     * @throws JsonException
85
     * @throws AbstractApiException
86
     * @throws InvalidModelException
87
     */
88
    public function update(AbstractModel $model): ?AbstractModel
89
    {
90
        $model = $this->repository->update($model);
91
        if ($model?->isCacheable()) {
92
            return $this->cacheModel($model);
93
        }
94
95
        return $model;
96
    }
97
98
    /**
99
     * @throws InvalidArgumentException
100
     * @throws JsonException
101
     * @throws AbstractApiException
102
     * @throws InvalidModelException
103
     */
104
    public function delete(AbstractModel $model): void
105
    {
106
        $this->repository->delete($model);
107
        if ($model->isCacheable()) {
108
            $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
109
            $this->cache->delete($cache_key);
110
            if ($this->supportsTagging()) {
111
                $this->cache->invalidateTags([$model->getEntityName() . "_search_results"]);
112
            }
113
        }
114
    }
115
116
    /**
117
     * @throws JsonException
118
     * @throws InvalidArgumentException
119
     */
120
    public function search(): ArrayCollection
121
    {
122
        $cache_key = $this->generator->generate(
123
            $this->repository->getEndpoint(),
124
            json_encode(static::$filter_params, JSON_THROW_ON_ERROR)
125
        );
126
127
        return $this->cache->get($cache_key, function(ItemInterface $item) {
128
            $search_results = $this->doSearch();
129
            if (!$search_results->isEmpty()) {
130
                $item->set($search_results);
131
                $item->expiresAfter($this->getCacheTTL());
132
                if ($this->supportsTagging()) {
133
                    $item->tag([$search_results->first()->getEntityName() . "_search_results"]);
134
                }
135
            }
136
137
            return $search_results;
138
        });
139
    }
140
141
    public function getClient(): ClientInterface
142
    {
143
        return $this->repository->getClient();
144
    }
145
146
    public function getAllowedFilters(): array
147
    {
148
        return $this->repository->getAllowedFilters();
149
    }
150
151
    public function getRelationClassMap(): array
152
    {
153
        return $this->repository->getRelationClassMap();
154
    }
155
156
    public function getEndpoint(): string
157
    {
158
        return $this->repository->getEndpoint();
159
    }
160
161
    public function constructEndpointUrl(string $endpoint, array $params): string
162
    {
163
        return $this->repository->constructEndpointUrl($endpoint, $params);
164
    }
165
166
    public function addRelationToFetch(string $relation): void
167
    {
168
        $this->repository->addRelationToFetch($relation);
169
    }
170
171
    public function getModelClass(): string
172
    {
173
        return $this->repository->getModelClass();
174
    }
175
176
    public function __call(string $method, array $args): mixed
177
    {
178
        if (method_exists($this->repository, $method)) {
179
            return call_user_func_array([$this->repository, $method], $args);
180
        }
181
182
        return null;
183
    }
184
185
    /**
186
     * @throws InvalidArgumentException
187
     */
188
    protected function cacheModel(AbstractModel $model): ?AbstractModel
189
    {
190
        $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
191
192
        $this->cache->delete($cache_key);
193
        return $this->cache->get($cache_key, function(ItemInterface $item) use ($model) {
194
            $item->set($model);
195
            $item->expiresAfter($this->getCacheTTL());
196
            if ($this->supportsTagging()) {
197
                $this->cache->invalidateTags([$model->getEntityName() . "_search_results"]);
198
                $item->tag([$model->getEntityName()]);
199
            }
200
201
            return $model;
202
        });
203
    }
204
205
    protected function getCacheTTL(): int
206
    {
207
        return $this->options[ClientOptions::CLIENT_OPTIONS_CACHE_TTL] ?? ClientOptions::CACHE_DEFAULT_TTL;
208
    }
209
210
    protected function supportsTagging(): bool
211
    {
212
        return $this->cache instanceof TagAwareCacheInterface;
213
    }
214
215
    public static  function getRelationClassFor(string $relation): ?string
216
    {
217
        return null;
218
    }
219
220
    public function getFetchRelations(): array
221
    {
222
        return $this->repository->getFetchRelations();
223
    }
224
}
225