1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Http\Client\Common\Plugin; |
4
|
|
|
|
5
|
|
|
use Http\Client\Common\Plugin; |
6
|
|
|
use Http\Client\Common\Plugin\Exception\RewindStreamException; |
7
|
|
|
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator; |
8
|
|
|
use Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator; |
9
|
|
|
use Http\Client\Common\Plugin\Cache\Listener\CacheListener; |
10
|
|
|
use Http\Message\StreamFactory; |
11
|
|
|
use Http\Promise\FulfilledPromise; |
12
|
|
|
use Psr\Cache\CacheItemInterface; |
13
|
|
|
use Psr\Cache\CacheItemPoolInterface; |
14
|
|
|
use Psr\Http\Message\RequestInterface; |
15
|
|
|
use Psr\Http\Message\ResponseInterface; |
16
|
|
|
use Psr\Http\Message\StreamFactoryInterface; |
17
|
|
|
use Symfony\Component\OptionsResolver\Options; |
18
|
|
|
use Symfony\Component\OptionsResolver\OptionsResolver; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Allow for caching a response with a PSR-6 compatible caching engine. |
22
|
|
|
* |
23
|
|
|
* It can follow the RFC-7234 caching specification or use a fixed cache lifetime. |
24
|
|
|
* |
25
|
|
|
* @author Tobias Nyholm <[email protected]> |
26
|
|
|
*/ |
27
|
|
|
final class CachePlugin implements Plugin |
28
|
|
|
{ |
29
|
|
|
use VersionBridgePlugin; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var CacheItemPoolInterface |
33
|
|
|
*/ |
34
|
|
|
private $pool; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var StreamFactory|StreamFactoryInterface |
38
|
|
|
*/ |
39
|
|
|
private $streamFactory; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
private $config; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Cache directives indicating if a response can not be cached. |
48
|
|
|
* |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
private $noCacheFlags = ['no-cache', 'private', 'no-store']; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param CacheItemPoolInterface $pool |
55
|
|
|
* @param StreamFactory|StreamFactoryInterface $streamFactory |
56
|
|
|
* @param array $config { |
57
|
|
|
* |
58
|
|
|
* @var bool $respect_cache_headers Whether to look at the cache directives or ignore them |
59
|
|
|
* @var int $default_ttl (seconds) If we do not respect cache headers or can't calculate a good ttl, use this |
60
|
|
|
* value |
61
|
|
|
* @var string $hash_algo The hashing algorithm to use when generating cache keys |
62
|
|
|
* @var int $cache_lifetime (seconds) To support serving a previous stale response when the server answers 304 |
63
|
|
|
* we have to store the cache for a longer time than the server originally says it is valid for. |
64
|
|
|
* We store a cache item for $cache_lifetime + max age of the response. |
65
|
|
|
* @var array $methods list of request methods which can be cached |
66
|
|
|
* @var array $blacklisted_paths list of regex patterns of paths explicitly not to be cached |
67
|
|
|
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses |
68
|
|
|
* @var CacheKeyGenerator $cache_key_generator an object to generate the cache key. Defaults to a new instance of SimpleGenerator |
69
|
|
|
* @var CacheListener[] $cache_listeners an array of objects to act on the response based on the results of the cache check. |
70
|
|
|
* Defaults to an empty array |
71
|
|
|
* } |
72
|
|
|
*/ |
73
|
16 |
|
public function __construct(CacheItemPoolInterface $pool, $streamFactory, array $config = []) |
74
|
|
|
{ |
75
|
16 |
|
if (!($streamFactory instanceof StreamFactory) && !($streamFactory instanceof StreamFactoryInterface)) { |
|
|
|
|
76
|
|
|
throw new \TypeError(\sprintf('Argument 2 passed to %s::__construct() must be of type %s|%s, %s given.', self::class, StreamFactory::class, StreamFactoryInterface::class, \is_object($streamFactory) ? \get_class($streamFactory) : \gettype($streamFactory))); |
|
|
|
|
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
$this->pool = $pool; |
80
|
|
|
$this->streamFactory = $streamFactory; |
81
|
|
|
|
82
|
|
|
if (isset($config['respect_cache_headers']) && isset($config['respect_response_cache_directives'])) { |
83
|
|
|
throw new \InvalidArgumentException('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives". Use "respect_response_cache_directives" instead.'); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
$optionsResolver = new OptionsResolver(); |
87
|
|
|
$this->configureOptions($optionsResolver); |
88
|
|
|
$this->config = $optionsResolver->resolve($config); |
89
|
|
|
|
90
|
|
|
if (null === $this->config['cache_key_generator']) { |
91
|
|
|
$this->config['cache_key_generator'] = new SimpleGenerator(); |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* This method will setup the cachePlugin in client cache mode. When using the client cache mode the plugin will |
97
|
|
|
* cache responses with `private` cache directive. |
98
|
|
|
* |
99
|
|
|
* @param CacheItemPoolInterface $pool |
100
|
|
|
* @param StreamFactory|StreamFactoryInterface $streamFactory |
101
|
|
|
* @param array $config For all possible config options see the constructor docs |
102
|
|
|
* |
103
|
|
|
* @return CachePlugin |
104
|
|
|
*/ |
105
|
|
|
public static function clientCache(CacheItemPoolInterface $pool, $streamFactory, array $config = []) |
106
|
|
|
{ |
107
|
|
|
// Allow caching of private requests |
108
|
|
|
if (isset($config['respect_response_cache_directives'])) { |
109
|
|
|
$config['respect_response_cache_directives'][] = 'no-cache'; |
110
|
|
|
$config['respect_response_cache_directives'][] = 'max-age'; |
111
|
|
|
$config['respect_response_cache_directives'] = array_unique($config['respect_response_cache_directives']); |
112
|
|
|
} else { |
113
|
|
|
$config['respect_response_cache_directives'] = ['no-cache', 'max-age']; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return new self($pool, $streamFactory, $config); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* This method will setup the cachePlugin in server cache mode. This is the default caching behavior it refuses to |
121
|
|
|
* cache responses with the `private`or `no-cache` directives. |
122
|
|
|
* |
123
|
|
|
* @param CacheItemPoolInterface $pool |
124
|
|
|
* @param StreamFactory|StreamFactoryInterface $streamFactory |
125
|
|
|
* @param array $config For all possible config options see the constructor docs |
126
|
|
|
* |
127
|
|
|
* @return CachePlugin |
128
|
|
|
*/ |
129
|
|
|
public static function serverCache(CacheItemPoolInterface $pool, $streamFactory, array $config = []) |
130
|
|
|
{ |
131
|
|
|
return new self($pool, $streamFactory, $config); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
protected function doHandleRequest(RequestInterface $request, callable $next, callable $first) |
|
|
|
|
135
|
|
|
{ |
136
|
|
|
$method = strtoupper($request->getMethod()); |
137
|
|
|
// if the request not is cachable, move to $next |
138
|
|
|
if (!in_array($method, $this->config['methods'])) { |
139
|
|
|
return $next($request)->then(function (ResponseInterface $response) use ($request) { |
140
|
|
|
$response = $this->handleCacheListeners($request, $response, false, null); |
141
|
|
|
|
142
|
|
|
return $response; |
143
|
|
|
}); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// If we can cache the request |
147
|
|
|
$key = $this->createCacheKey($request); |
148
|
|
|
$cacheItem = $this->pool->getItem($key); |
149
|
|
|
|
150
|
|
|
if ($cacheItem->isHit()) { |
151
|
|
|
$data = $cacheItem->get(); |
152
|
|
|
// The array_key_exists() is to be removed in 2.0. |
153
|
|
|
if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) { |
154
|
|
|
// This item is still valid according to previous cache headers |
155
|
|
|
$response = $this->createResponseFromCacheItem($cacheItem); |
156
|
|
|
$response = $this->handleCacheListeners($request, $response, true, $cacheItem); |
157
|
|
|
|
158
|
|
|
return new FulfilledPromise($response); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Add headers to ask the server if this cache is still valid |
162
|
|
|
if ($modifiedSinceValue = $this->getModifiedSinceHeaderValue($cacheItem)) { |
163
|
|
|
$request = $request->withHeader('If-Modified-Since', $modifiedSinceValue); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if ($etag = $this->getETag($cacheItem)) { |
167
|
|
|
$request = $request->withHeader('If-None-Match', $etag); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return $next($request)->then(function (ResponseInterface $response) use ($request, $cacheItem) { |
172
|
|
|
if (304 === $response->getStatusCode()) { |
173
|
|
|
if (!$cacheItem->isHit()) { |
174
|
|
|
/* |
175
|
|
|
* We do not have the item in cache. This plugin did not add If-Modified-Since |
176
|
|
|
* or If-None-Match headers. Return the response from server. |
177
|
|
|
*/ |
178
|
|
|
return $this->handleCacheListeners($request, $response, false, $cacheItem); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// The cached response we have is still valid |
182
|
|
|
$data = $cacheItem->get(); |
183
|
|
|
$maxAge = $this->getMaxAge($response); |
184
|
|
|
$data['expiresAt'] = $this->calculateResponseExpiresAt($maxAge); |
185
|
|
|
$cacheItem->set($data)->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge)); |
186
|
|
|
$this->pool->save($cacheItem); |
187
|
|
|
|
188
|
|
|
return $this->handleCacheListeners($request, $this->createResponseFromCacheItem($cacheItem), true, $cacheItem); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
if ($this->isCacheable($response) && $this->isCacheableRequest($request)) { |
192
|
|
|
$bodyStream = $response->getBody(); |
193
|
|
|
$body = $bodyStream->__toString(); |
194
|
|
|
if ($bodyStream->isSeekable()) { |
195
|
|
|
$bodyStream->rewind(); |
196
|
|
|
} else { |
197
|
|
|
$response = $response->withBody($this->streamFactory->createStream($body)); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$maxAge = $this->getMaxAge($response); |
201
|
|
|
$cacheItem |
202
|
|
|
->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge)) |
203
|
|
|
->set([ |
204
|
|
|
'response' => $response, |
205
|
|
|
'body' => $body, |
206
|
|
|
'expiresAt' => $this->calculateResponseExpiresAt($maxAge), |
207
|
|
|
'createdAt' => time(), |
208
|
|
|
'etag' => $response->getHeader('ETag'), |
209
|
|
|
]); |
210
|
|
|
$this->pool->save($cacheItem); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $this->handleCacheListeners($request, $response, false, isset($cacheItem) ? $cacheItem : null); |
214
|
|
|
}); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Calculate the timestamp when this cache item should be dropped from the cache. The lowest value that can be |
219
|
|
|
* returned is $maxAge. |
220
|
|
|
* |
221
|
|
|
* @param int|null $maxAge |
222
|
|
|
* |
223
|
|
|
* @return int|null Unix system time passed to the PSR-6 cache |
224
|
|
|
*/ |
225
|
|
|
private function calculateCacheItemExpiresAfter($maxAge) |
226
|
|
|
{ |
227
|
|
|
if (null === $this->config['cache_lifetime'] && null === $maxAge) { |
228
|
|
|
return; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $this->config['cache_lifetime'] + $maxAge; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Calculate the timestamp when a response expires. After that timestamp, we need to send a |
236
|
|
|
* If-Modified-Since / If-None-Match request to validate the response. |
237
|
|
|
* |
238
|
|
|
* @param int|null $maxAge |
239
|
|
|
* |
240
|
|
|
* @return int|null Unix system time. A null value means that the response expires when the cache item expires |
241
|
|
|
*/ |
242
|
|
|
private function calculateResponseExpiresAt($maxAge) |
243
|
|
|
{ |
244
|
|
|
if (null === $maxAge) { |
245
|
|
|
return; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
return time() + $maxAge; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Verify that we can cache this response. |
253
|
|
|
* |
254
|
|
|
* @param ResponseInterface $response |
255
|
|
|
* |
256
|
|
|
* @return bool |
257
|
|
|
*/ |
258
|
|
|
protected function isCacheable(ResponseInterface $response) |
259
|
|
|
{ |
260
|
|
|
if (!in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 404, 410])) { |
261
|
|
|
return false; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
$nocacheDirectives = array_intersect($this->config['respect_response_cache_directives'], $this->noCacheFlags); |
265
|
|
|
foreach ($nocacheDirectives as $nocacheDirective) { |
266
|
|
|
if ($this->getCacheControlDirective($response, $nocacheDirective)) { |
267
|
|
|
return false; |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
return true; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Verify that we can cache this request. |
276
|
|
|
* |
277
|
|
|
* @param RequestInterface $request |
278
|
|
|
* |
279
|
|
|
* @return bool |
280
|
|
|
*/ |
281
|
|
|
private function isCacheableRequest(RequestInterface $request) |
282
|
|
|
{ |
283
|
|
|
foreach ($this->config['blacklisted_paths'] as $not_to_cache_path) { |
284
|
|
|
if (1 === preg_match('/'.$not_to_cache_path.'/', $request->getUri())) { |
285
|
|
|
return false; |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
return true; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Get the value of a parameter in the cache control header. |
294
|
|
|
* |
295
|
|
|
* @param ResponseInterface $response |
296
|
|
|
* @param string $name The field of Cache-Control to fetch |
297
|
|
|
* |
298
|
|
|
* @return bool|string The value of the directive, true if directive without value, false if directive not present |
299
|
|
|
*/ |
300
|
|
|
private function getCacheControlDirective(ResponseInterface $response, $name) |
301
|
|
|
{ |
302
|
|
|
$headers = $response->getHeader('Cache-Control'); |
303
|
|
|
foreach ($headers as $header) { |
304
|
|
|
if (preg_match(sprintf('|%s=?([0-9]+)?|i', $name), $header, $matches)) { |
305
|
|
|
// return the value for $name if it exists |
306
|
|
|
if (isset($matches[1])) { |
307
|
|
|
return $matches[1]; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return true; |
311
|
|
|
} |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
return false; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param RequestInterface $request |
319
|
|
|
* |
320
|
|
|
* @return string |
321
|
|
|
*/ |
322
|
|
|
private function createCacheKey(RequestInterface $request) |
323
|
|
|
{ |
324
|
|
|
$key = $this->config['cache_key_generator']->generate($request); |
325
|
|
|
|
326
|
|
|
return hash($this->config['hash_algo'], $key); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Get a ttl in seconds. It could return null if we do not respect cache headers and got no defaultTtl. |
331
|
|
|
* |
332
|
|
|
* @param ResponseInterface $response |
333
|
|
|
* |
334
|
|
|
* @return int|null |
335
|
|
|
*/ |
336
|
|
|
private function getMaxAge(ResponseInterface $response) |
337
|
|
|
{ |
338
|
|
|
if (!in_array('max-age', $this->config['respect_response_cache_directives'], true)) { |
339
|
|
|
return $this->config['default_ttl']; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// check for max age in the Cache-Control header |
343
|
|
|
$maxAge = $this->getCacheControlDirective($response, 'max-age'); |
344
|
|
|
if (!is_bool($maxAge)) { |
345
|
|
|
$ageHeaders = $response->getHeader('Age'); |
346
|
|
|
foreach ($ageHeaders as $age) { |
347
|
|
|
return $maxAge - ((int) $age); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
return (int) $maxAge; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
// check for ttl in the Expires header |
354
|
|
|
$headers = $response->getHeader('Expires'); |
355
|
|
|
foreach ($headers as $header) { |
356
|
|
|
return (new \DateTime($header))->getTimestamp() - (new \DateTime())->getTimestamp(); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $this->config['default_ttl']; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Configure an options resolver. |
364
|
|
|
* |
365
|
|
|
* @param OptionsResolver $resolver |
366
|
|
|
*/ |
367
|
|
|
private function configureOptions(OptionsResolver $resolver) |
368
|
|
|
{ |
369
|
|
|
$resolver->setDefaults([ |
370
|
|
|
'cache_lifetime' => 86400 * 30, // 30 days |
371
|
|
|
'default_ttl' => 0, |
372
|
|
|
//Deprecated as of v1.3, to be removed in v2.0. Use respect_response_cache_directives instead |
373
|
|
|
'respect_cache_headers' => null, |
374
|
|
|
'hash_algo' => 'sha1', |
375
|
|
|
'methods' => ['GET', 'HEAD'], |
376
|
|
|
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'], |
377
|
|
|
'cache_key_generator' => null, |
378
|
|
|
'cache_listeners' => [], |
379
|
|
|
'blacklisted_paths' => [], |
380
|
|
|
]); |
381
|
|
|
|
382
|
|
|
$resolver->setAllowedTypes('cache_lifetime', ['int', 'null']); |
383
|
|
|
$resolver->setAllowedTypes('default_ttl', ['int', 'null']); |
384
|
|
|
$resolver->setAllowedTypes('respect_cache_headers', ['bool', 'null']); |
385
|
|
|
$resolver->setAllowedTypes('methods', 'array'); |
386
|
|
|
$resolver->setAllowedTypes('cache_key_generator', ['null', 'Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator']); |
387
|
|
|
$resolver->setAllowedTypes('blacklisted_paths', 'array'); |
388
|
|
|
$resolver->setAllowedValues('hash_algo', hash_algos()); |
389
|
|
|
$resolver->setAllowedValues('methods', function ($value) { |
390
|
|
|
/* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */ |
391
|
|
|
$matches = preg_grep('/[^A-Z0-9!#$%&\'*+\-.^_`|~]/', $value); |
392
|
|
|
|
393
|
|
|
return empty($matches); |
394
|
|
|
}); |
395
|
|
|
$resolver->setAllowedTypes('cache_listeners', ['array']); |
396
|
|
|
|
397
|
|
|
$resolver->setNormalizer('respect_cache_headers', function (Options $options, $value) { |
398
|
|
|
if (null !== $value) { |
399
|
|
|
@trigger_error('The option "respect_cache_headers" is deprecated since version 1.3 and will be removed in 2.0. Use "respect_response_cache_directives" instead.', E_USER_DEPRECATED); |
|
|
|
|
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
return null === $value ? true : $value; |
403
|
|
|
}); |
404
|
|
|
|
405
|
|
|
$resolver->setNormalizer('respect_response_cache_directives', function (Options $options, $value) { |
406
|
|
|
if (false === $options['respect_cache_headers']) { |
407
|
|
|
return []; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
return $value; |
411
|
|
|
}); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* @param CacheItemInterface $cacheItem |
416
|
|
|
* |
417
|
|
|
* @return ResponseInterface |
418
|
|
|
*/ |
419
|
|
|
private function createResponseFromCacheItem(CacheItemInterface $cacheItem) |
420
|
|
|
{ |
421
|
|
|
$data = $cacheItem->get(); |
422
|
|
|
|
423
|
|
|
/** @var ResponseInterface $response */ |
424
|
|
|
$response = $data['response']; |
425
|
|
|
$stream = $this->streamFactory->createStream($data['body']); |
426
|
|
|
|
427
|
|
|
try { |
428
|
|
|
$stream->rewind(); |
429
|
|
|
} catch (\Exception $e) { |
430
|
|
|
throw new RewindStreamException('Cannot rewind stream.', 0, $e); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
$response = $response->withBody($stream); |
434
|
|
|
|
435
|
|
|
return $response; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Get the value of the "If-Modified-Since" header. |
440
|
|
|
* |
441
|
|
|
* @param CacheItemInterface $cacheItem |
442
|
|
|
* |
443
|
|
|
* @return string|null |
444
|
|
|
*/ |
445
|
|
|
private function getModifiedSinceHeaderValue(CacheItemInterface $cacheItem) |
446
|
|
|
{ |
447
|
|
|
$data = $cacheItem->get(); |
448
|
|
|
// The isset() is to be removed in 2.0. |
449
|
|
|
if (!isset($data['createdAt'])) { |
450
|
|
|
return; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
$modified = new \DateTime('@'.$data['createdAt']); |
454
|
|
|
$modified->setTimezone(new \DateTimeZone('GMT')); |
455
|
|
|
|
456
|
|
|
return sprintf('%s GMT', $modified->format('l, d-M-y H:i:s')); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Get the ETag from the cached response. |
461
|
|
|
* |
462
|
|
|
* @param CacheItemInterface $cacheItem |
463
|
|
|
* |
464
|
|
|
* @return string|null |
465
|
|
|
*/ |
466
|
|
|
private function getETag(CacheItemInterface $cacheItem) |
467
|
|
|
{ |
468
|
|
|
$data = $cacheItem->get(); |
469
|
|
|
// The isset() is to be removed in 2.0. |
470
|
|
|
if (!isset($data['etag'])) { |
471
|
|
|
return; |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
foreach ($data['etag'] as $etag) { |
475
|
|
|
if (!empty($etag)) { |
476
|
|
|
return $etag; |
477
|
|
|
} |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Call the cache listeners, if they are set. |
483
|
|
|
* |
484
|
|
|
* @param RequestInterface $request |
485
|
|
|
* @param ResponseInterface $response |
486
|
|
|
* @param bool $cacheHit |
487
|
|
|
* @param CacheItemInterface|null $cacheItem |
488
|
|
|
* |
489
|
|
|
* @return ResponseInterface |
490
|
|
|
*/ |
491
|
|
|
private function handleCacheListeners(RequestInterface $request, ResponseInterface $response, $cacheHit, $cacheItem) |
492
|
|
|
{ |
493
|
|
|
foreach ($this->config['cache_listeners'] as $cacheListener) { |
494
|
|
|
$response = $cacheListener->onCacheResponse($request, $response, $cacheHit, $cacheItem); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
return $response; |
498
|
|
|
} |
499
|
|
|
} |
500
|
|
|
|
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.