CacheableRepository::actingAs()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 11
rs 10
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] =
41
                $user->getLogin();
42
        } else {
43
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] =
44
                $user;
45
        }
46
47
        return $this;
48
    }
49
50
    /**
51
     * @throws InvalidArgumentException
52
     */
53
    public function get(mixed $id): ?AbstractModel
54
    {
55
        $cache_key = $this->generator->generate($this->repository->getModelClass(), $id);
56
        return $this->cache->get($cache_key, function (ItemInterface $item) use ($id) {
57
            $model = $this->repository->get($id);
58
            if ($model) {
59
                $item->set($model);
60
                $item->expiresAfter($this->getCacheTTL());
61
                $item->tag([$model->getEntityName()]);
62
            }
63
64
            return $model;
65
        });
66
    }
67
68
    /**
69
     * @throws InvalidArgumentException
70
     * @throws JsonException
71
     * @throws AbstractApiException
72
     * @throws InvalidModelException
73
     */
74
    public function create(AbstractModel $model): ?AbstractModel
75
    {
76
        $model = $this->repository->create($model);
77
        if ($model?->isCacheable()) {
78
            return $this->cacheModel($model);
79
        }
80
81
        return $model;
82
    }
83
84
    /**
85
     * @throws InvalidArgumentException
86
     * @throws JsonException
87
     * @throws AbstractApiException
88
     * @throws InvalidModelException
89
     */
90
    public function update(AbstractModel $model): ?AbstractModel
91
    {
92
        $model = $this->repository->update($model);
93
        if ($model?->isCacheable()) {
94
            return $this->cacheModel($model);
95
        }
96
97
        return $model;
98
    }
99
100
    /**
101
     * @throws InvalidArgumentException
102
     * @throws JsonException
103
     * @throws AbstractApiException
104
     * @throws InvalidModelException
105
     */
106
    public function delete(AbstractModel $model): void
107
    {
108
        $this->repository->delete($model);
109
        if ($model->isCacheable()) {
110
            $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
111
            $this->cache->delete($cache_key);
112
            if ($this->supportsTagging()) {
113
                $this->cache->invalidateTags([$model->getEntityName() . "_search_results"]);
114
            }
115
        }
116
    }
117
118
    /**
119
     * @throws JsonException
120
     * @throws InvalidArgumentException
121
     */
122
    public function search(): ArrayCollection
123
    {
124
        $cache_key = $this->generator->generate(
125
            $this->repository->getEndpoint(),
126
            json_encode(static::$filter_params, JSON_THROW_ON_ERROR)
127
        );
128
129
        return $this->cache->get($cache_key, function (ItemInterface $item) {
130
            $search_results = $this->doSearch();
131
            if (!$search_results->isEmpty()) {
132
                $item->set($search_results);
133
                $item->expiresAfter($this->getCacheTTL());
134
                if ($this->supportsTagging()) {
135
                    $item->tag([$search_results->first()->getEntityName() . "_search_results"]);
136
                }
137
            }
138
139
            return $search_results;
140
        });
141
    }
142
143
    public function getClient(): ClientInterface
144
    {
145
        return $this->repository->getClient();
146
    }
147
148
    public function getAllowedFilters(): array
149
    {
150
        return $this->repository->getAllowedFilters();
151
    }
152
153
    public function getRelationClassMap(): array
154
    {
155
        return $this->repository->getRelationClassMap();
156
    }
157
158
    public function getEndpoint(): string
159
    {
160
        return $this->repository->getEndpoint();
161
    }
162
163
    public function constructEndpointUrl(string $endpoint, array $params): string
164
    {
165
        return $this->repository->constructEndpointUrl($endpoint, $params);
166
    }
167
168
    public function addRelationToFetch(string $relation): void
169
    {
170
        $this->repository->addRelationToFetch($relation);
171
    }
172
173
    public function getModelClass(): string
174
    {
175
        return $this->repository->getModelClass();
176
    }
177
178
    public function __call(string $method, array $args): mixed
179
    {
180
        if (method_exists($this->repository, $method)) {
181
            return call_user_func_array([$this->repository, $method], $args);
182
        }
183
184
        return null;
185
    }
186
187
    /**
188
     * @throws InvalidArgumentException
189
     */
190
    protected function cacheModel(AbstractModel $model): ?AbstractModel
191
    {
192
        $cache_key = $this->generator->generate($model->getEntityName(), $model->getId());
193
194
        $this->cache->delete($cache_key);
195
        return $this->cache->get($cache_key, function (ItemInterface $item) use ($model) {
196
            $item->set($model);
197
            $item->expiresAfter($this->getCacheTTL());
198
            if ($this->supportsTagging()) {
199
                $this->cache->invalidateTags([$model->getEntityName() . "_search_results"]);
200
                $item->tag([$model->getEntityName()]);
201
            }
202
203
            return $model;
204
        });
205
    }
206
207
    protected function getCacheTTL(): int
208
    {
209
        return $this->options[ClientOptions::CLIENT_OPTIONS_CACHE_TTL] ?? ClientOptions::CACHE_DEFAULT_TTL;
210
    }
211
212
    protected function supportsTagging(): bool
213
    {
214
        return $this->cache instanceof TagAwareCacheInterface;
215
    }
216
217
    public static function getRelationClassFor(string $relation): ?string
218
    {
219
        return null;
220
    }
221
222
    public function getFetchRelations(): array
223
    {
224
        return $this->repository->getFetchRelations();
225
    }
226
}
227