Completed
Push — 1.x ( e77a21...7e6c69 )
by Akihito
18s queued 16s
created

ResourceStorage::get()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 5
c 1
b 1
f 0
nc 1
nop 1
dl 0
loc 8
ccs 2
cts 2
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\QueryRepository;
6
7
use BEAR\RepositoryModule\Annotation\EtagPool;
8
use BEAR\RepositoryModule\Annotation\ResourceObjectPool;
9
use BEAR\Resource\AbstractUri;
10
use BEAR\Resource\RequestInterface;
11
use BEAR\Resource\ResourceObject;
12
use Ray\Di\Di\Set;
13
use Ray\Di\ProviderInterface;
14
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
15
use Symfony\Contracts\Cache\ItemInterface;
16
17
use function array_merge;
18
use function array_unique;
19
use function assert;
20
use function explode;
21
use function implode;
22
use function is_array;
23
use function is_string;
24
use function sprintf;
25
use function strtoupper;
26
27
/**
28
 * @psalm-type Props = array{
29
 *     logger: RepositoryLoggerInterface,
30
 *     purger:PurgerInterface,
31
 *     uriTag: UriTag,
32
 *     saver: ResourceStorageSaver,
33 30
 *     roProvider:ProviderInterface<TagAwareAdapterInterface>,
34
 *     etagProvider: ProviderInterface<TagAwareAdapterInterface>
35 30
 * }
36 30
 */
37
final class ResourceStorage implements ResourceStorageInterface
38
{
39
    /**
40
     * Resource object cache prefix
41 3
     */
42
    private const KEY_RO = 'ro-';
43 3
44
    /**
45
     * Resource static cache prifix
46
     */
47
    private const KEY_DONUT = 'donut-';
48
49 26
    /** @var ProviderInterface<TagAwareAdapterInterface> */
50
    private ProviderInterface $roPoolProvider;
51 26
52 26
    /** @var ProviderInterface<TagAwareAdapterInterface> */
53 26
    private ProviderInterface $etagPoolProvider;
54
    private TagAwareAdapterInterface $roPool;
55 26
    private TagAwareAdapterInterface $etagPool;
56
57 26
    /**
58
     * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider
59 26
     * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider
60 26
     */
61
    public function __construct(
62
        private RepositoryLoggerInterface $logger,
63
        private PurgerInterface $purger,
64
        private UriTagInterface $uriTag,
65 27
        private ResourceStorageSaver $saver,
66
        #[Set(TagAwareAdapterInterface::class, ResourceObjectPool::class)]
67 27
        ProviderInterface $roPoolProvider,
68 27
        #[Set(TagAwareAdapterInterface::class, EtagPool::class)]
69
        ProviderInterface $etagPoolProvider,
70 27
    ) {
71 27
        $this->initializePools($roPoolProvider, $etagPoolProvider);
72
    }
73
74
    /**
75
     * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider
76 25
     * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider
77
     */
78 25
    private function initializePools(ProviderInterface $roPoolProvider, ProviderInterface $etagPoolProvider): void
79
    {
80 25
        $this->roPoolProvider = $roPoolProvider;
81
        $this->etagPoolProvider = $etagPoolProvider;
82
        $this->roPool = $roPoolProvider->get();
83
        $etagPool = $this->etagPoolProvider->get();
84
        $this->etagPool = $etagPool instanceof TagAwareAdapterInterface ? $etagPool : $this->roPool; // @phpstan-ignore-line
85
    }
86 12
87
    /**
88 12
     * {@inheritDoc}
89 12
     */
90
    public function get(AbstractUri $uri): ResourceState|null
91 12
    {
92
        $item = $this->roPool->getItem($this->getUriKey($uri, self::KEY_RO));
93
        assert($item instanceof ItemInterface);
94
        $state = $item->get();
95
        assert($state instanceof ResourceState || $state === null);
96
97 24
        return $state;
98
    }
99 24
100 24
    public function getDonut(AbstractUri $uri): ResourceDonut|null
101 24
    {
102
        $key = $this->getUriKey($uri, self::KEY_DONUT);
103 24
        $item = $this->roPool->getItem($key);
104
        assert($item instanceof ItemInterface);
105
        $donut = $item->get();
106
        assert($donut instanceof ResourceDonut || $donut === null);
107
108
        return $donut;
109 2
    }
110
111 2
    /**
112 2
     * {@inheritDoc}
113 2
     */
114
    public function hasEtag(string $etag): bool
115 2
    {
116
        return $this->etagPool->hasItem($etag);
117
    }
118 26
119
    /**
120 26
     * {@inheritDoc}
121 8
     */
122
    public function deleteEtag(AbstractUri $uri)
123 18
    {
124 18
        $uriTag = ($this->uriTag)($uri);
125 2
126
        return $this->invalidateTags([$uriTag]);
127
    }
128
129 18
    /**
130
     * {@inheritDoc}
131
     */
132 27
    public function invalidateTags(array $tags): bool
133
    {
134 27
        $tag = $tags !== [] ? implode(' ', $tags) : '';
135 18
        $this->logger->log('invalidate-etag tags:%s', $tag);
136
        $valid1 = $this->roPool->invalidateTags($tags);
137 10
        $valid2 = $this->etagPool->invalidateTags($tags);
138 10
        ($this->purger)(implode(' ', $tags));
139 10
140 10
        return $valid1 && $valid2;
141 10
    }
142 10
143
    /**
144
     * {@inheritDoc}
145
     *
146 10
     * @return bool
147
     */
148
    public function saveValue(ResourceObject $ro, int $ttl)
149
    {
150
        /** @psalm-suppress MixedAssignment $body */
151
        $body = $this->evaluateBody($ro->body);
152
        $value = ResourceState::create($ro, $body, null);
153
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
154
        $tags = $this->getTags($ro);
155
        $this->logger->log('save-value uri:%s tags:%s ttl:%s', $ro->uri, $tags, $ttl);
156
157
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
158
    }
159
160
    /**
161
     * {@inheritDoc}
162
     *
163
     * @return bool
164
     */
165
    public function saveView(ResourceObject $ro, int $ttl)
166
    {
167
        $this->logger->log('save-view uri:%s ttl:%s', $ro->uri, $ttl);
168
        /** @psalm-suppress MixedAssignment $body */
169
        $body = $this->evaluateBody($ro->body);
170
        $value = ResourceState::create($ro, $body, $ro->view);
171
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
172
        $tags = $this->getTags($ro);
173
174
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
175
    }
176
177
    /**
178
     * {@inheritDoc}
179
     */
180
    public function saveDonut(AbstractUri $uri, ResourceDonut $donut, int|null $sMaxAge, array $headerKeys): void
181
    {
182
        $key = $this->getUriKey($uri, self::KEY_DONUT);
183
        $this->logger->log('save-donut uri:%s s-maxage:%s', $uri, $sMaxAge);
184
        $result = $this->saver->__invoke($key, $donut, $this->roPool, $headerKeys, $sMaxAge);
185
        assert($result, 'Donut save failed.');
186
    }
187
188
    public function saveDonutView(ResourceObject $ro, int|null $ttl): bool
189
    {
190
        $resourceState = ResourceState::create($ro, [], $ro->view);
191
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
192
        $tags = $this->getTags($ro);
193
        $this->logger->log('save-donut-view uri:%s surrogate-keys:%s s-maxage:%s', $ro->uri, $tags, $ttl);
194
195
        return $this->saver->__invoke($key, $resourceState, $this->roPool, $tags, $ttl);
196
    }
197
198
    /** @return list<string> */
0 ignored issues
show
Bug introduced by
The type BEAR\QueryRepository\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
199
    private function getTags(ResourceObject $ro): array
200
    {
201
        $etag = $ro->headers['ETag'];
202
        $tags = [$etag, ($this->uriTag)($ro->uri)];
203
        if (isset($ro->headers[Header::SURROGATE_KEY])) {
204
            $tags = array_merge($tags, explode(' ', $ro->headers[Header::SURROGATE_KEY]));
205
        }
206
207
        /** @var list<string> $uniqueTags */
208
        $uniqueTags = array_unique($tags);
209
210
        return $uniqueTags;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $uniqueTags returns the type BEAR\QueryRepository\list which is incompatible with the type-hinted return array.
Loading history...
211
    }
212
213
    private function evaluateBody(mixed $body): mixed
214
    {
215
        if (! is_array($body)) {
216
            return $body;
217
        }
218
219
        /** @psalm-suppress MixedAssignment $item */
220
        foreach ($body as &$item) {
221
            if ($item instanceof RequestInterface) {
222
                $item = ($item)();
223
            }
224
225
            if ($item instanceof ResourceObject) {
226
                $item->body = $this->evaluateBody($item->body);
227
            }
228
        }
229
230
        return $body;
231
    }
232
233
    private function getUriKey(AbstractUri $uri, string $type): string
234
    {
235
        return $type . ($this->uriTag)($uri) . (isset($_SERVER['X_VARY']) ? $this->getVary() : '');
236
    }
237
238
    private function getVary(): string
239
    {
240
        $xvary = $_SERVER['X_VARY'];
241
        $varys = explode(',', $xvary);
242
        $varyString = '';
243
        foreach ($varys as $vary) {
244
            $phpVaryKey = sprintf('X_%s', strtoupper($vary));
245
            if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) {
246
                $varyString .= $_SERVER[$phpVaryKey];
247
            }
248
        }
249
250
        return $varyString;
251
    }
252
253
    public function saveEtag(AbstractUri $uri, string $etag, string $surrogateKeys, int|null $ttl): void
254
    {
255
        $tags = $surrogateKeys !== '' ? explode(' ', $surrogateKeys) : [];
256
        $tags[] = (new UriTag())($uri);
257
        /** @var list<string> $uniqueTags */
258
        $uniqueTags = array_unique($tags);
259
        $this->logger->log('save-etag uri:%s etag:%s surrogate-keys:%s', $uri, $etag, $uniqueTags);
260
        $this->saver->__invoke($etag, 'etag', $this->etagPool, $uniqueTags, $ttl);
0 ignored issues
show
Bug introduced by
$uniqueTags of type BEAR\QueryRepository\list is incompatible with the type array expected by parameter $tags of BEAR\QueryRepository\Res...torageSaver::__invoke(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

260
        $this->saver->__invoke($etag, 'etag', $this->etagPool, /** @scrutinizer ignore-type */ $uniqueTags, $ttl);
Loading history...
261
    }
262
263
    public function __serialize(): array
264
    {
265
        return [
266
            'logger' => $this->logger,
267
            'purger' => $this->purger,
268
            'uriTag' => $this->uriTag,
269
            'saver' => $this->saver,
270
            'roProvider' => $this->roPoolProvider,
271
            'etagProvider' => $this->etagPoolProvider,
272
        ];
273
    }
274
275
    /**
276
     * @param Props $data
0 ignored issues
show
Bug introduced by
The type BEAR\QueryRepository\Props was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
277
     *
278
     * @return void
279
     */
280
    public function __unserialize(array $data): void
281
    {
282
        $this->logger = $data['logger'];
283
        $this->purger = $data['purger'];
284
        $this->uriTag = $data['uriTag'];
285
        $this->saver = $data['saver'];
286
        $this->initializePools($data['roProvider'], $data['etagProvider']);
287
    }
288
}
289