1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace BEAR\QueryRepository; |
6
|
|
|
|
7
|
|
|
use BEAR\QueryRepository\Exception\ExpireAtKeyNotExists; |
8
|
|
|
use BEAR\RepositoryModule\Annotation\Cacheable; |
9
|
|
|
use BEAR\RepositoryModule\Annotation\HttpCache; |
10
|
|
|
use BEAR\Resource\AbstractUri; |
11
|
|
|
use BEAR\Resource\RequestInterface; |
12
|
|
|
use BEAR\Resource\ResourceObject; |
13
|
|
|
use Doctrine\Common\Annotations\Reader; |
14
|
|
|
|
15
|
|
|
final class QueryRepository implements QueryRepositoryInterface |
16
|
|
|
{ |
17
|
|
|
const ETAG_BY_URI = 'etag-by-uri'; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var ResourceStorageInterface |
21
|
|
|
*/ |
22
|
|
|
private $storage; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var Reader |
26
|
|
|
*/ |
27
|
|
|
private $reader; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var Expiry |
31
|
|
|
*/ |
32
|
|
|
private $expiry; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var EtagSetterInterface |
36
|
|
|
*/ |
37
|
|
|
private $setEtag; |
38
|
|
|
|
39
|
26 |
|
public function __construct( |
40
|
|
|
EtagSetterInterface $setEtag, |
41
|
|
|
ResourceStorageInterface $storage, |
42
|
|
|
Reader $reader, |
43
|
|
|
Expiry $expiry |
44
|
|
|
) { |
45
|
26 |
|
$this->setEtag = $setEtag; |
46
|
26 |
|
$this->reader = $reader; |
47
|
26 |
|
$this->storage = $storage; |
48
|
26 |
|
$this->expiry = $expiry; |
49
|
26 |
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* {@inheritdoc} |
53
|
|
|
* |
54
|
|
|
* @throws \ReflectionException |
55
|
|
|
*/ |
56
|
24 |
|
public function put(ResourceObject $ro) |
57
|
|
|
{ |
58
|
24 |
|
$ro->toString(); |
59
|
24 |
|
$httpCache = $this->getHttpCacheAnnotation($ro); |
60
|
24 |
|
$cacheable = $this->getCacheableAnnotation($ro); |
61
|
24 |
|
($this->setEtag)($ro, null, $httpCache); |
62
|
24 |
|
$lifeTime = $this->getExpiryTime($ro, $cacheable); |
63
|
23 |
|
if (isset($ro->headers['ETag'])) { |
64
|
23 |
|
$this->storage->updateEtag($ro, $lifeTime); |
65
|
|
|
} |
66
|
23 |
|
$this->setMaxAge($ro, $lifeTime); |
67
|
23 |
|
if ($cacheable instanceof Cacheable && $cacheable->type === 'view') { |
|
|
|
|
68
|
2 |
|
return $this->saveViewCache($ro, $lifeTime); |
69
|
|
|
} |
70
|
|
|
|
71
|
21 |
|
return $this->storage->saveValue($ro, $lifeTime); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* {@inheritdoc} |
76
|
|
|
*/ |
77
|
22 |
|
public function get(AbstractUri $uri) |
78
|
|
|
{ |
79
|
22 |
|
$data = $this->storage->get($uri); |
80
|
22 |
|
if ($data === false) { |
81
|
20 |
|
return false; |
82
|
|
|
} |
83
|
14 |
|
$age = \time() - \strtotime($data[2]['Last-Modified']); |
84
|
14 |
|
$data[2]['Age'] = $age; |
85
|
|
|
|
86
|
14 |
|
return $data; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* {@inheritdoc} |
91
|
|
|
*/ |
92
|
12 |
|
public function purge(AbstractUri $uri) |
93
|
|
|
{ |
94
|
12 |
|
$this->storage->deleteEtag($uri); |
95
|
|
|
|
96
|
12 |
|
return $this->storage->delete($uri); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @throws \ReflectionException |
101
|
|
|
*/ |
102
|
24 |
|
private function getHttpCacheAnnotation(ResourceObject $ro) |
103
|
|
|
{ |
104
|
24 |
|
$annotation = $this->reader->getClassAnnotation(new \ReflectionClass($ro), HttpCache::class); |
105
|
24 |
|
if ($annotation instanceof HttpCache || $annotation === null) { |
|
|
|
|
106
|
24 |
|
return $annotation; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
throw new \LogicException(); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @throws \ReflectionException |
114
|
|
|
* |
115
|
|
|
* @return ?Cacheable |
|
|
|
|
116
|
|
|
*/ |
117
|
24 |
|
private function getCacheableAnnotation(ResourceObject $ro) |
118
|
|
|
{ |
119
|
24 |
|
$annotation = $this->reader->getClassAnnotation(new \ReflectionClass($ro), Cacheable::class); |
120
|
24 |
|
if ($annotation instanceof Cacheable || $annotation === null) { |
|
|
|
|
121
|
24 |
|
return $annotation; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
throw new \LogicException(); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
private function evaluateBody($body) |
|
|
|
|
128
|
|
|
{ |
129
|
|
|
if (! \is_array($body)) { |
130
|
|
|
return $body; |
131
|
|
|
} |
132
|
|
|
foreach ($body as &$item) { |
133
|
|
|
if ($item instanceof RequestInterface) { |
134
|
|
|
$item = ($item)(); |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
return $body; |
139
|
|
|
} |
140
|
|
|
|
141
|
25 |
|
private function getExpiryTime(ResourceObject $ro, Cacheable $cacheable = null) : int |
142
|
|
|
{ |
143
|
25 |
|
if ($cacheable === null) { |
144
|
4 |
|
return 0; |
145
|
|
|
} |
146
|
|
|
|
147
|
21 |
|
if ($cacheable->expiryAt) { |
148
|
2 |
|
return $this->getExpiryAtSec($ro, $cacheable); |
149
|
|
|
} |
150
|
|
|
|
151
|
19 |
|
return $cacheable->expirySecond ? $cacheable->expirySecond : $this->expiry[$cacheable->expiry]; |
152
|
|
|
} |
153
|
|
|
|
154
|
2 |
|
private function getExpiryAtSec(ResourceObject $ro, Cacheable $cacheable) : int |
155
|
|
|
{ |
156
|
2 |
|
if (! isset($ro->body[$cacheable->expiryAt])) { |
157
|
1 |
|
$msg = \sprintf('%s::%s', \get_class($ro), $cacheable->expiryAt); |
158
|
|
|
|
159
|
1 |
|
throw new ExpireAtKeyNotExists($msg); |
160
|
|
|
} |
161
|
1 |
|
$expiryAt = $ro->body[$cacheable->expiryAt]; |
162
|
|
|
|
163
|
1 |
|
return \strtotime($expiryAt) - \time(); |
164
|
|
|
} |
165
|
|
|
|
166
|
23 |
|
private function setMaxAge(ResourceObject $ro, int $age) |
167
|
|
|
{ |
168
|
23 |
|
$setMaxAge = \sprintf('max-age=%d', $age); |
169
|
23 |
|
if (isset($ro->headers['Cache-Control'])) { |
170
|
6 |
|
$ro->headers['Cache-Control'] .= ', ' . $setMaxAge; |
171
|
|
|
|
172
|
6 |
|
return; |
173
|
|
|
} |
174
|
20 |
|
$ro->headers['Cache-Control'] = $setMaxAge; |
175
|
20 |
|
} |
176
|
|
|
|
177
|
2 |
|
private function saveViewCache(ResourceObject $ro, int $lifeTime) |
178
|
|
|
{ |
179
|
2 |
|
if (! $ro->view) { |
|
|
|
|
180
|
|
|
$ro->view = $ro->toString(); |
181
|
|
|
} |
182
|
|
|
|
183
|
2 |
|
return $this->storage->saveView($ro, $lifeTime); |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.