Passed
Pull Request — 1.x (#100)
by Akihito
02:20
created

ResourceStorage::setInjector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
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\KnownTagTtl;
9
use BEAR\Resource\AbstractUri;
10
use BEAR\Resource\RequestInterface;
11
use BEAR\Resource\ResourceObject;
12
use Doctrine\Common\Cache\CacheProvider;
13
use Psr\Cache\CacheItemPoolInterface;
14
use Ray\Di\Di\Inject;
15
use Ray\Di\InjectorInterface;
16
use Ray\PsrCacheModule\Annotation\Shared;
17
use Serializable;
18
use Symfony\Component\Cache\Adapter\AdapterInterface;
19
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
20
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
21
use Symfony\Contracts\Cache\ItemInterface;
22
23
use function array_merge;
24
use function array_unique;
25
use function assert;
26
use function explode;
27
use function implode;
28
use function is_array;
29
use function is_string;
30
use function sprintf;
31
use function strtoupper;
32
33 30
final class ResourceStorage implements ResourceStorageInterface, Serializable
34
{
35 30
    use ResourceStorageCacheableTrait;
36 30
37
    /**
38
     * Resource object cache prefix
39
     */
40
    private const KEY_RO = 'ro-';
41 3
42
    /**
43 3
     * Resource static cache prifix
44
     */
45
    private const KEY_DONUT = 'donut-';
46
47
    /** @var RepositoryLoggerInterface */
48
    private $logger;
49 26
50
    /** @var TagAwareAdapter */
51 26
    private $roPool;
52 26
53 26
    /** @var TagAwareAdapter */
54
    private $etagPool;
55 26
56
    /** @var PurgerInterface */
57 26
    private $purger;
58
59 26
    /** @var UriTagInterface */
60 26
    private $uriTag;
61
62
    /** @var ResourceStorageSaver */
63
    private $saver;
64
65 27
    /** @var float */
66
    private $knownTagTtl;
67 27
68 27
    /**
69
     * @Shared("pool")
70 27
     * @EtagPool("etagPool")
71 27
     * @KnownTagTtl("knownTagTtl")
72
     */
73
    #[Shared('pool'), EtagPool('etagPool'), KnownTagTtl('knownTagTtl')]
74
    public function __construct(
75
        RepositoryLoggerInterface $logger,
76 25
        PurgerInterface $etagDeleter,
77
        UriTagInterface $uriTag,
78 25
        ?CacheItemPoolInterface $pool = null,
79
        ?CacheItemPoolInterface $etagPool = null,
80 25
        ?CacheProvider $cache = null,
81
        float $knownTagTtl = 0.0
82
    ) {
83
        $this->logger = $logger;
84
        $this->purger = $etagDeleter;
85
        $this->uriTag = $uriTag;
86 12
        $this->saver = new ResourceStorageSaver();
87
        if ($pool === null && $cache instanceof CacheProvider) {
88 12
            $this->injectDoctrineCache($cache);
89 12
90
            return;
91 12
        }
92
93
        $this->knownTagTtl = $knownTagTtl;
94
        assert($pool instanceof AdapterInterface);
95
        $etagPool =  $etagPool instanceof AdapterInterface ? $etagPool : $pool;
96
        $this->roPool = new TagAwareAdapter($pool, $etagPool, $knownTagTtl);
97 24
        $this->etagPool = new TagAwareAdapter($etagPool, $etagPool, $knownTagTtl);
98
    }
99 24
100 24
    /**
101 24
     * @Inject
102
     */
103 24
    #[Inject]
104
    public function setInjector(InjectorInterface $injector): void
105
    {
106
        $this->injector = $injector;
107
    }
108
109 2
    private function injectDoctrineCache(CacheProvider $cache): void
110
    {
111 2
        $this->roPool = new TagAwareAdapter(new DoctrineAdapter($cache));
112 2
        $this->etagPool = $this->roPool;
113 2
    }
114
115 2
    /**
116
     * {@inheritdoc}
117
     */
118 26
    public function get(AbstractUri $uri): ?ResourceState
119
    {
120 26
        $item = $this->roPool->getItem($this->getUriKey($uri, self::KEY_RO));
121 8
        assert($item instanceof ItemInterface);
122
        $state = $item->get();
123 18
        assert($state instanceof ResourceState || $state === null);
124 18
125 2
        return $state;
126
    }
127
128
    public function getDonut(AbstractUri $uri): ?ResourceDonut
129 18
    {
130
        $key = $this->getUriKey($uri, self::KEY_DONUT);
131
        $item = $this->roPool->getItem($key);
132 27
        assert($item instanceof ItemInterface);
133
        $donut = $item->get();
134 27
        assert($donut instanceof ResourceDonut || $donut === null);
135 18
136
        return $donut;
137 10
    }
138 10
139 10
    /**
140 10
     * {@inheritdoc}
141 10
     */
142 10
    public function hasEtag(string $etag): bool
143
    {
144
        return $this->etagPool->hasItem($etag);
145
    }
146 10
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function deleteEtag(AbstractUri $uri)
151
    {
152
        $uriTag = ($this->uriTag)($uri);
153
154
        return $this->invalidateTags([$uriTag]);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function invalidateTags(array $tags): bool
161
    {
162
        $tag = $tags ? implode(' ', $tags) : '';
163
        $this->logger->log('invalidate-etag tags:%s', $tag);
164
        $valid1 = $this->roPool->invalidateTags($tags);
165
        $valid2 = $this->etagPool->invalidateTags($tags);
166
        ($this->purger)(implode(' ', $tags));
167
168
        return $valid1 && $valid2;
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     *
174
     * @return bool
175
     */
176
    public function saveValue(ResourceObject $ro, int $ttl)
177
    {
178
        /** @psalm-suppress MixedAssignment $body */
179
        $body = $this->evaluateBody($ro->body);
180
        $value = ResourceState::create($ro, $body, null);
181
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
182
        $tags = $this->getTags($ro);
183
        $this->logger->log('save-value uri:%s tags:%s ttl:%s', $ro->uri, $tags, $ttl);
184
185
        return $this->saver->__invoke($key, $value, $this->roPool, $ro->uri, $tags, $ttl);
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     *
191
     * @return bool
192
     */
193
    public function saveView(ResourceObject $ro, int $ttl)
194
    {
195
        $this->logger->log('save-view uri:%s ttl:%s', $ro->uri, $ttl);
196
        /** @psalm-suppress MixedAssignment $body */
197
        $body = $this->evaluateBody($ro->body);
198
        $value = ResourceState::create($ro, $body, $ro->view);
199
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
200
        $tags = $this->getTags($ro);
201
202
        return $this->saver->__invoke($key, $value, $this->roPool, $ro->uri, $tags, $ttl);
203
    }
204
205
    public function saveDonut(AbstractUri $uri, ResourceDonut $donut, ?int $sMaxAge): void
206
    {
207
        $key = $this->getUriKey($uri, self::KEY_DONUT);
208
        $this->logger->log('save-donut uri:%s s-maxage:%s', $uri, $sMaxAge);
209
210
        $this->saver->__invoke($key, $donut, $this->roPool, $uri, [], $sMaxAge);
211
    }
212
213
    public function saveDonutView(ResourceObject $ro, ?int $ttl): bool
214
    {
215
        $resourceState = ResourceState::create($ro, [], $ro->view);
216
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
217
        $tags = $this->getTags($ro);
218
        $this->logger->log('save-donut-view uri:%s surrogate-keys:%s s-maxage:%s', $ro->uri, $tags, $ttl);
219
220
        return $this->saver->__invoke($key, $resourceState, $this->roPool, $ro->uri, $tags, $ttl);
221
    }
222
223
    /**
224
     * @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...
225
     */
226
    private function getTags(ResourceObject $ro): array
227
    {
228
        $etag = $ro->headers['ETag'];
229
        $tags = [$etag, ($this->uriTag)($ro->uri)];
230
        if (isset($ro->headers[Header::SURROGATE_KEY])) {
231
            $tags = array_merge($tags, explode(' ', $ro->headers[Header::SURROGATE_KEY]));
232
        }
233
234
        /** @var list<string> $uniqueTags */
235
        $uniqueTags = array_unique($tags);
236
237
        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...
238
    }
239
240
    /**
241
     * @param mixed $body
242
     *
243
     * @return mixed
244
     */
245
    private function evaluateBody($body)
246
    {
247
        if (! is_array($body)) {
248
            return $body;
249
        }
250
251
        /** @psalm-suppress MixedAssignment $item */
252
        foreach ($body as &$item) {
253
            if ($item instanceof RequestInterface) {
254
                $item = ($item)();
255
            }
256
257
            if ($item instanceof ResourceObject) {
258
                $item->body = $this->evaluateBody($item->body);
259
            }
260
        }
261
262
        return $body;
263
    }
264
265
    private function getUriKey(AbstractUri $uri, string $type): string
266
    {
267
        return $type . ($this->uriTag)($uri) . (isset($_SERVER['X_VARY']) ? $this->getVary() : '');
268
    }
269
270
    private function getVary(): string
271
    {
272
        $xvary = $_SERVER['X_VARY'];
273
        assert(is_string($xvary));
274
        $varys = explode(',', $xvary);
275
        $varyString = '';
276
        foreach ($varys as $vary) {
277
            $phpVaryKey = sprintf('X_%s', strtoupper($vary));
278
            if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) {
279
                $varyString .= $_SERVER[$phpVaryKey];
280
            }
281
        }
282
283
        return $varyString;
284
    }
285
286
    public function saveEtag(AbstractUri $uri, string $etag, string $surrogateKeys, ?int $ttl): void
287
    {
288
        $tags = $surrogateKeys ? explode(' ', $surrogateKeys) : [];
289
        $tags[] = (new UriTag())($uri);
290
        /** @var list<string> $uniqueTags */
291
        $uniqueTags = array_unique($tags);
292
        $this->logger->log('save-etag uri:%s etag:%s surrogate-keys:%s', $uri, $etag, $uniqueTags);
293
        $this->saver->__invoke($etag, 'etag', $this->etagPool, $uri, $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

293
        $this->saver->__invoke($etag, 'etag', $this->etagPool, $uri, /** @scrutinizer ignore-type */ $uniqueTags, $ttl);
Loading history...
294
    }
295
}
296