Passed
Pull Request — 1.x (#144)
by Akihito
15:43 queued 12:45
created

ResourceStorage::__serialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
final class ResourceStorage implements ResourceStorageInterface
28
{
29
    /**
30
     * Resource object cache prefix
31
     */
32
    private const KEY_RO = 'ro-';
33 30
34
    /**
35 30
     * Resource static cache prifix
36 30
     */
37
    private const KEY_DONUT = 'donut-';
38
39
    /** @var ProviderInterface<TagAwareAdapterInterface> */
40
    private ProviderInterface $roPoolProvider;
41 3
42
    /** @var ProviderInterface<TagAwareAdapterInterface> */
43 3
    private ProviderInterface $etagPoolProvider;
44
    private TagAwareAdapterInterface $roPool;
45
    private TagAwareAdapterInterface $etagPool;
46
47
    /**
48
     * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider
49 26
     * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider
50
     */
51 26
    public function __construct(
52 26
        private RepositoryLoggerInterface $logger,
53 26
        private PurgerInterface $purger,
54
        private UriTagInterface $uriTag,
55 26
        private ResourceStorageSaver $saver,
56
        #[Set(TagAwareAdapterInterface::class, ResourceObjectPool::class)]
57 26
        ProviderInterface $roPoolProvider,
58
        #[Set(TagAwareAdapterInterface::class, EtagPool::class)]
59 26
        ProviderInterface $etagPoolProvider,
60 26
    ) {
61
        $this->initializePools($roPoolProvider, $etagPoolProvider);
62
    }
63
64
    /**
65 27
     * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider
66
     * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider
67 27
     */
68 27
    private function initializePools(ProviderInterface $roPoolProvider, ProviderInterface $etagPoolProvider): void
69
    {
70 27
        $this->roPoolProvider = $roPoolProvider;
71 27
        $this->etagPoolProvider = $etagPoolProvider;
72
        $this->roPool = $roPoolProvider->get();
73
        $etagPool = $this->etagPoolProvider->get();
74
        $this->etagPool = $etagPool instanceof NullTagAwareAdapter ?  $this->roPool : $etagPool;
75
    }
76 25
77
    /**
78 25
     * {@inheritDoc}
79
     */
80 25
    public function get(AbstractUri $uri): ResourceState|null
81
    {
82
        $item = $this->roPool->getItem($this->getUriKey($uri, self::KEY_RO));
83
        assert($item instanceof ItemInterface);
84
        $state = $item->get();
85
        assert($state instanceof ResourceState || $state === null);
86 12
87
        return $state;
88 12
    }
89 12
90
    public function getDonut(AbstractUri $uri): ResourceDonut|null
91 12
    {
92
        $key = $this->getUriKey($uri, self::KEY_DONUT);
93
        $item = $this->roPool->getItem($key);
94
        assert($item instanceof ItemInterface);
95
        $donut = $item->get();
96
        assert($donut instanceof ResourceDonut || $donut === null);
97 24
98
        return $donut;
99 24
    }
100 24
101 24
    /**
102
     * {@inheritDoc}
103 24
     */
104
    public function hasEtag(string $etag): bool
105
    {
106
        return $this->etagPool->hasItem($etag);
107
    }
108
109 2
    /**
110
     * {@inheritDoc}
111 2
     */
112 2
    public function deleteEtag(AbstractUri $uri)
113 2
    {
114
        $uriTag = ($this->uriTag)($uri);
115 2
116
        return $this->invalidateTags([$uriTag]);
117
    }
118 26
119
    /**
120 26
     * {@inheritDoc}
121 8
     */
122
    public function invalidateTags(array $tags): bool
123 18
    {
124 18
        $tag = $tags !== [] ? implode(' ', $tags) : '';
125 2
        $this->logger->log('invalidate-etag tags:%s', $tag);
126
        $valid1 = $this->roPool->invalidateTags($tags);
127
        $valid2 = $this->etagPool->invalidateTags($tags);
128
        ($this->purger)(implode(' ', $tags));
129 18
130
        return $valid1 && $valid2;
131
    }
132 27
133
    /**
134 27
     * {@inheritDoc}
135 18
     *
136
     * @return bool
137 10
     */
138 10
    public function saveValue(ResourceObject $ro, int $ttl)
139 10
    {
140 10
        /** @psalm-suppress MixedAssignment $body */
141 10
        $body = $this->evaluateBody($ro->body);
142 10
        $value = ResourceState::create($ro, $body, null);
143
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
144
        $tags = $this->getTags($ro);
145
        $this->logger->log('save-value uri:%s tags:%s ttl:%s', $ro->uri, $tags, $ttl);
146 10
147
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
148
    }
149
150
    /**
151
     * {@inheritDoc}
152
     *
153
     * @return bool
154
     */
155
    public function saveView(ResourceObject $ro, int $ttl)
156
    {
157
        $this->logger->log('save-view uri:%s ttl:%s', $ro->uri, $ttl);
158
        /** @psalm-suppress MixedAssignment $body */
159
        $body = $this->evaluateBody($ro->body);
160
        $value = ResourceState::create($ro, $body, $ro->view);
161
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
162
        $tags = $this->getTags($ro);
163
164
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170
    public function saveDonut(AbstractUri $uri, ResourceDonut $donut, int|null $sMaxAge, array $headerKeys): void
171
    {
172
        $key = $this->getUriKey($uri, self::KEY_DONUT);
173
        $this->logger->log('save-donut uri:%s s-maxage:%s', $uri, $sMaxAge);
174
        $result = $this->saver->__invoke($key, $donut, $this->roPool, $headerKeys, $sMaxAge);
175
        assert($result, 'Donut save failed.');
176
    }
177
178
    public function saveDonutView(ResourceObject $ro, int|null $ttl): bool
179
    {
180
        $resourceState = ResourceState::create($ro, [], $ro->view);
181
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
182
        $tags = $this->getTags($ro);
183
        $this->logger->log('save-donut-view uri:%s surrogate-keys:%s s-maxage:%s', $ro->uri, $tags, $ttl);
184
185
        return $this->saver->__invoke($key, $resourceState, $this->roPool, $tags, $ttl);
186
    }
187
188
    /** @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...
189
    private function getTags(ResourceObject $ro): array
190
    {
191
        $etag = $ro->headers['ETag'];
192
        $tags = [$etag, ($this->uriTag)($ro->uri)];
193
        if (isset($ro->headers[Header::SURROGATE_KEY])) {
194
            $tags = array_merge($tags, explode(' ', $ro->headers[Header::SURROGATE_KEY]));
195
        }
196
197
        /** @var list<string> $uniqueTags */
198
        $uniqueTags = array_unique($tags);
199
200
        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...
201
    }
202
203
    private function evaluateBody(mixed $body): mixed
204
    {
205
        if (! is_array($body)) {
206
            return $body;
207
        }
208
209
        /** @psalm-suppress MixedAssignment $item */
210
        foreach ($body as &$item) {
211
            if ($item instanceof RequestInterface) {
212
                $item = ($item)();
213
            }
214
215
            if ($item instanceof ResourceObject) {
216
                $item->body = $this->evaluateBody($item->body);
217
            }
218
        }
219
220
        return $body;
221
    }
222
223
    private function getUriKey(AbstractUri $uri, string $type): string
224
    {
225
        return $type . ($this->uriTag)($uri) . (isset($_SERVER['X_VARY']) ? $this->getVary() : '');
226
    }
227
228
    private function getVary(): string
229
    {
230
        $xvary = $_SERVER['X_VARY'];
231
        $varys = explode(',', $xvary);
232
        $varyString = '';
233
        foreach ($varys as $vary) {
234
            $phpVaryKey = sprintf('X_%s', strtoupper($vary));
235
            if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) {
236
                $varyString .= $_SERVER[$phpVaryKey];
237
            }
238
        }
239
240
        return $varyString;
241
    }
242
243
    public function saveEtag(AbstractUri $uri, string $etag, string $surrogateKeys, int|null $ttl): void
244
    {
245
        $tags = $surrogateKeys !== '' ? explode(' ', $surrogateKeys) : [];
246
        $tags[] = (new UriTag())($uri);
247
        /** @var list<string> $uniqueTags */
248
        $uniqueTags = array_unique($tags);
249
        $this->logger->log('save-etag uri:%s etag:%s surrogate-keys:%s', $uri, $etag, $uniqueTags);
250
        $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

250
        $this->saver->__invoke($etag, 'etag', $this->etagPool, /** @scrutinizer ignore-type */ $uniqueTags, $ttl);
Loading history...
251
    }
252
253
    public function __serialize(): array
254
    {
255
        return [
256
            'logger' => $this->logger,
257
            'purger' => $this->purger,
258
            'uriTag' => $this->uriTag,
259
            'saver' => $this->saver,
260
            'roProvider' => $this->roPoolProvider,
261
            'etagProvider' => $this->etagPoolProvider,
262
        ];
263
    }
264
265
    /**
266
     * @param array{
267
     *     logger: RepositoryLoggerInterface,
268
     *     purger:PurgerInterface,
269
     *     uriTag: UriTag,
270
     *      saver: ResourceStorageSaver,
271
     *     roProvider:ProviderInterface<TagAwareAdapterInterface>,
272
     *     etagProvider: ProviderInterface<TagAwareAdapterInterface>
273
     * } $data
274
     *
275
     * @return void
276
     */
277
    public function __unserialize(array $data): void
278
    {
279
        $this->logger = $data['logger'];
280
        $this->purger = $data['purger'];
281
        $this->uriTag = $data['uriTag'];
282
        $this->saver = $data['saver'];
283
        $this->initializePools($data['roProvider'], $data['etagProvider']);
284
    }
285
}
286