Passed
Pull Request — 1.x (#99)
by Akihito
10:41
created

ResourceStorage   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 15
Bugs 1 Features 3
Metric Value
eloc 91
c 15
b 1
f 3
dl 0
loc 246
ccs 52
cts 52
cp 1
rs 9.76
wmc 33

16 Methods

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

275
        $this->saver->__invoke($etag, 'etag', $this->etagPool, $uri, /** @scrutinizer ignore-type */ $uniqueTags, $ttl);
Loading history...
276
    }
277
}
278