Passed
Pull Request — 1.x (#113)
by Akihito
04:44 queued 02:30
created

ResourceStorage::__construct()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 4
eloc 14
c 6
b 0
f 1
nc 3
nop 8
dl 0
loc 27
ccs 11
cts 11
cp 1
crap 4
rs 9.7998

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\QueryRepository;
6
7
use BEAR\QueryRepository\Annotation\IsOptimizeCache;
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\Component\Cache\Adapter\TagAwareAdapter;
19
use Symfony\Contracts\Cache\ItemInterface;
20
21
use function array_merge;
22
use function array_unique;
23
use function assert;
24
use function explode;
25
use function implode;
26
use function is_array;
27
use function is_string;
28
use function sprintf;
29
use function strtoupper;
30
31
final class ResourceStorage implements ResourceStorageInterface
32
{
33 30
    use ResourceStorageCacheableTrait;
34
35 30
    /**
36 30
     * Resource object cache prefix
37
     */
38
    private const KEY_RO = 'ro-';
39
40
    /**
41 3
     * Resource static cache prifix
42
     */
43 3
    private const KEY_DONUT = 'donut-';
44
45
    /** @var RepositoryLoggerInterface */
46
    private $logger;
47
48
    /** @var TagAwareAdapter */
49 26
    private $roPool;
50
51 26
    /** @var TagAwareAdapter */
52 26
    private $etagPool;
53 26
54
    /** @var PurgerInterface */
55 26
    private $purger;
56
57 26
    /** @var UriTagInterface */
58
    private $uriTag;
59 26
60 26
    /** @var ResourceStorageSaver */
61
    private $saver;
62
63
    /** @var float */
64
    private $knownTagTtl;
65 27
66
    /** @var bool */
67 27
    private $isOptimizeCache;
68 27
69
    /**
70 27
     * @Shared("pool")
71 27
     * @EtagPool("etagPool")
72
     * @KnownTagTtl("knownTagTtl")
73
     * @IsOptimizeCache("isOptimizeCache")
74
     */
75
    #[Shared('pool'), EtagPool('etagPool'), KnownTagTtl('knownTagTtl'), IsOptimizeCache('isOptimizeCache')]
76 25
    public function __construct(
77
        RepositoryLoggerInterface $logger,
78 25
        PurgerInterface $etagDeleter,
79
        UriTagInterface $uriTag,
80 25
        ?CacheItemPoolInterface $pool = null,
81
        ?CacheItemPoolInterface $etagPool = null,
82
        ?CacheProvider $cache = null,
83
        float $knownTagTtl = 0.0,
84
        bool $isOptimizeCache = false
85
    ) {
86 12
        $this->logger = $logger;
87
        $this->purger = $etagDeleter;
88 12
        $this->uriTag = $uriTag;
89 12
        $this->saver = new ResourceStorageSaver();
90
        if ($pool === null && $cache instanceof CacheProvider) {
91 12
            $this->injectDoctrineCache($cache);
92
93
            return;
94
        }
95
96
        $this->knownTagTtl = $knownTagTtl;
97 24
        assert($pool instanceof AdapterInterface);
98
        $etagPool =  $etagPool instanceof AdapterInterface ? $etagPool : $pool;
99 24
        $this->roPool = new TagAwareAdapter($pool, $etagPool, $knownTagTtl);
100 24
        $this->etagPool = new TagAwareAdapter($etagPool, $etagPool, $knownTagTtl);
101 24
        $this->isOptimizeCache = $isOptimizeCache;
102
    }
103 24
104
    private function injectDoctrineCache(CacheProvider $cache): void
105
    {
106
        /** @psalm-suppress DeprecatedClass */
107
        $this->roPool = new TagAwareAdapter(new DoctrineAdapter($cache));
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Cache\Adapter\DoctrineAdapter has been deprecated: Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead ( Ignorable by Annotation )

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

107
        $this->roPool = new TagAwareAdapter(/** @scrutinizer ignore-deprecated */ new DoctrineAdapter($cache));
Loading history...
108
        $this->etagPool = $this->roPool;
109 2
    }
110
111 2
    /**
112 2
     * {@inheritdoc}
113 2
     */
114
    public function get(AbstractUri $uri): ?ResourceState
115 2
    {
116
        $item = $this->roPool->getItem($this->getUriKey($uri, self::KEY_RO));
117
        assert($item instanceof ItemInterface);
118 26
        $state = $item->get();
119
        assert($state instanceof ResourceState || $state === null);
120 26
121 8
        return $state;
122
    }
123 18
124 18
    public function getDonut(AbstractUri $uri): ?ResourceDonut
125 2
    {
126
        $key = $this->getUriKey($uri, self::KEY_DONUT);
127
        $item = $this->roPool->getItem($key);
128
        assert($item instanceof ItemInterface);
129 18
        $donut = $item->get();
130
        assert($donut instanceof ResourceDonut || $donut === null);
131
132 27
        return $donut;
133
    }
134 27
135 18
    /**
136
     * {@inheritdoc}
137 10
     */
138 10
    public function hasEtag(string $etag): bool
139 10
    {
140 10
        return $this->etagPool->hasItem($etag);
141 10
    }
142 10
143
    /**
144
     * {@inheritdoc}
145
     */
146 10
    public function deleteEtag(AbstractUri $uri)
147
    {
148
        $uriTag = ($this->uriTag)($uri);
149
150
        return $this->invalidateTags([$uriTag]);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function invalidateTags(array $tags): bool
157
    {
158
        $tag = $tags !== [] ? implode(' ', $tags) : '';
159
        $this->logger->log('invalidate-etag tags:%s', $tag);
160
        $valid1 = $this->roPool->invalidateTags($tags);
161
        $valid2 = $this->etagPool->invalidateTags($tags);
162
        ($this->purger)(implode(' ', $tags));
163
164
        return $valid1 && $valid2;
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     *
170
     * @return bool
171
     */
172
    public function saveValue(ResourceObject $ro, int $ttl)
173
    {
174
        /** @psalm-suppress MixedAssignment $body */
175
        $body = $this->evaluateBody($ro->body);
176
        $value = ResourceState::create($ro, $body, null);
177
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
178
        $tags = $this->getTags($ro);
179
        $this->logger->log('save-value uri:%s tags:%s ttl:%s', $ro->uri, $tags, $ttl);
180
181
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     *
187
     * @return bool
188
     */
189
    public function saveView(ResourceObject $ro, int $ttl)
190
    {
191
        $this->logger->log('save-view uri:%s ttl:%s', $ro->uri, $ttl);
192
        /** @psalm-suppress MixedAssignment $body */
193
        $body = $this->evaluateBody($ro->body);
194
        $value = ResourceState::create($ro, $body, $ro->view);
195
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
196
        $tags = $this->getTags($ro);
197
198
        return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl);
199
    }
200
201
    public function saveDonut(AbstractUri $uri, ResourceDonut $donut, ?int $sMaxAge): void
202
    {
203
        $key = $this->getUriKey($uri, self::KEY_DONUT);
204
        $this->logger->log('save-donut uri:%s s-maxage:%s', $uri, $sMaxAge);
205
206
        $this->saver->__invoke($key, $donut, $this->roPool, [], $sMaxAge);
207
    }
208
209
    public function saveDonutView(ResourceObject $ro, ?int $ttl): bool
210
    {
211
        $body = $this->isOptimizeCache || ! is_array($ro->body) ? [] : $this->evaluateDonutBody($ro->body);
212
        $resourceState = ResourceState::create($ro, $body, $ro->view);
213
        $key = $this->getUriKey($ro->uri, self::KEY_RO);
214
        $tags = $this->getTags($ro);
215
        $this->logger->log('save-donut-view uri:%s surrogate-keys:%s s-maxage:%s', $ro->uri, $tags, $ttl);
216
217
        return $this->saver->__invoke($key, $resourceState, $this->roPool, $tags, $ttl);
218
    }
219
220
    /**
221
     * @return array<mixed>
222
     */
223
    private function evaluateDonutBody(array $body): array
224
    {
225
        foreach ($body as $key => $item) {
226
            if ($item instanceof DonutRequest) {
227
                $body[$key] = $item->getBody();
228
            }
229
        }
230
231
        return $body;
232
    }
233
234
    /**
235
     * @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...
236
     */
237
    private function getTags(ResourceObject $ro): array
238
    {
239
        $etag = $ro->headers['ETag'];
240
        $tags = [$etag, ($this->uriTag)($ro->uri)];
241
        if (isset($ro->headers[Header::SURROGATE_KEY])) {
242
            $tags = array_merge($tags, explode(' ', $ro->headers[Header::SURROGATE_KEY]));
243
        }
244
245
        /** @var list<string> $uniqueTags */
246
        $uniqueTags = array_unique($tags);
247
248
        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...
249
    }
250
251
    /**
252
     * @param mixed $body
253
     *
254
     * @return mixed
255
     */
256
    private function evaluateBody($body)
257
    {
258
        if (! is_array($body)) {
259
            return $body;
260
        }
261
262
        /** @psalm-suppress MixedAssignment $item */
263
        foreach ($body as &$item) {
264
            if ($item instanceof RequestInterface) {
265
                $item = ($item)();
266
            }
267
268
            if ($item instanceof ResourceObject) {
269
                $item->body = $this->evaluateBody($item->body);
270
            }
271
        }
272
273
        return $body;
274
    }
275
276
    private function getUriKey(AbstractUri $uri, string $type): string
277
    {
278
        return $type . ($this->uriTag)($uri) . (isset($_SERVER['X_VARY']) ? $this->getVary() : '');
279
    }
280
281
    private function getVary(): string
282
    {
283
        $xvary = $_SERVER['X_VARY'];
284
        assert(is_string($xvary));
285
        $varys = explode(',', $xvary);
286
        $varyString = '';
287
        foreach ($varys as $vary) {
288
            $phpVaryKey = sprintf('X_%s', strtoupper($vary));
289
            if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) {
290
                $varyString .= $_SERVER[$phpVaryKey];
291
            }
292
        }
293
294
        return $varyString;
295
    }
296
297
    public function saveEtag(AbstractUri $uri, string $etag, string $surrogateKeys, ?int $ttl): void
298
    {
299
        $tags = $surrogateKeys !== '' ? explode(' ', $surrogateKeys) : [];
300
        $tags[] = (new UriTag())($uri);
301
        /** @var list<string> $uniqueTags */
302
        $uniqueTags = array_unique($tags);
303
        $this->logger->log('save-etag uri:%s etag:%s surrogate-keys:%s', $uri, $etag, $uniqueTags);
304
        $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

304
        $this->saver->__invoke($etag, 'etag', $this->etagPool, /** @scrutinizer ignore-type */ $uniqueTags, $ttl);
Loading history...
305
    }
306
}
307