Open Journal Systems  3.3.0
CachePlugin.php
1 <?php
2 
3 namespace Guzzle\Plugin\Cache;
4 
14 use Doctrine\Common\Cache\ArrayCache;
16 
28 {
30  protected $revalidation;
31 
33  protected $canCache;
34 
36  protected $storage;
37 
39  protected $autoPurge;
40 
51  public function __construct($options = null)
52  {
53  if (!is_array($options)) {
54  if ($options instanceof CacheAdapterInterface) {
55  $options = array('storage' => new DefaultCacheStorage($options));
56  } elseif ($options instanceof CacheStorageInterface) {
57  $options = array('storage' => $options);
58  } elseif ($options) {
59  $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options)));
60  } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) {
61  // @codeCoverageIgnoreStart
62  throw new InvalidArgumentException('No cache was provided and Doctrine is not installed');
63  // @codeCoverageIgnoreEnd
64  }
65  }
66 
67  $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false;
68 
69  // Add a cache storage if a cache adapter was provided
70  $this->storage = isset($options['storage'])
71  ? $options['storage']
72  : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));
73 
74  if (!isset($options['can_cache'])) {
75  $this->canCache = new DefaultCanCacheStrategy();
76  } else {
77  $this->canCache = is_callable($options['can_cache'])
78  ? new CallbackCanCacheStrategy($options['can_cache'])
79  : $options['can_cache'];
80  }
81 
82  // Use the provided revalidation strategy or the default
83  $this->revalidation = isset($options['revalidation'])
84  ? $options['revalidation']
85  : new DefaultRevalidation($this->storage, $this->canCache);
86  }
87 
88  public static function getSubscribedEvents()
89  {
90  return array(
91  'request.before_send' => array('onRequestBeforeSend', -255),
92  'request.sent' => array('onRequestSent', 255),
93  'request.error' => array('onRequestError', 0),
94  'request.exception' => array('onRequestException', 0),
95  );
96  }
97 
103  public function onRequestBeforeSend(Event $event)
104  {
105  $request = $event['request'];
106  $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
107 
108  if (!$this->canCache->canCacheRequest($request)) {
109  switch ($request->getMethod()) {
110  case 'PURGE':
111  $this->purge($request);
112  $request->setResponse(new Response(200, array(), 'purged'));
113  break;
114  case 'PUT':
115  case 'POST':
116  case 'DELETE':
117  case 'PATCH':
118  if ($this->autoPurge) {
119  $this->purge($request);
120  }
121  }
122  return;
123  }
124 
125  if ($response = $this->storage->fetch($request)) {
126  $params = $request->getParams();
127  $params['cache.lookup'] = true;
128  $response->setHeader(
129  'Age',
130  time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now')
131  );
132  // Validate that the response satisfies the request
133  if ($this->canResponseSatisfyRequest($request, $response)) {
134  if (!isset($params['cache.hit'])) {
135  $params['cache.hit'] = true;
136  }
137  $request->setResponse($response);
138  }
139  }
140  }
141 
147  public function onRequestSent(Event $event)
148  {
149  $request = $event['request'];
150  $response = $event['response'];
151 
152  if ($request->getParams()->get('cache.hit') === null &&
153  $this->canCache->canCacheRequest($request) &&
154  $this->canCache->canCacheResponse($response)
155  ) {
156  $this->storage->cache($request, $response);
157  }
158 
159  $this->addResponseHeaders($request, $response);
160  }
161 
167  public function onRequestError(Event $event)
168  {
169  $request = $event['request'];
170 
171  if (!$this->canCache->canCacheRequest($request)) {
172  return;
173  }
174 
175  if ($response = $this->storage->fetch($request)) {
176  $response->setHeader(
177  'Age',
178  time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now')
179  );
180 
181  if ($this->canResponseSatisfyFailedRequest($request, $response)) {
182  $request->getParams()->set('cache.hit', 'error');
183  $this->addResponseHeaders($request, $response);
184  $event['response'] = $response;
185  $event->stopPropagation();
186  }
187  }
188  }
189 
197  public function onRequestException(Event $event)
198  {
199  if (!$event['exception'] instanceof CurlException) {
200  return;
201  }
202 
203  $request = $event['request'];
204  if (!$this->canCache->canCacheRequest($request)) {
205  return;
206  }
207 
208  if ($response = $this->storage->fetch($request)) {
209  $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now'));
210  if (!$this->canResponseSatisfyFailedRequest($request, $response)) {
211  return;
212  }
213  $request->getParams()->set('cache.hit', 'error');
214  $request->setResponse($response);
215  $this->addResponseHeaders($request, $response);
216  $event->stopPropagation();
217  }
218  }
219 
228  public function canResponseSatisfyRequest(RequestInterface $request, Response $response)
229  {
230  $responseAge = $response->calculateAge();
231  $reqc = $request->getHeader('Cache-Control');
232  $resc = $response->getHeader('Cache-Control');
233 
234  // Check the request's max-age header against the age of the response
235  if ($reqc && $reqc->hasDirective('max-age') &&
236  $responseAge > $reqc->getDirective('max-age')) {
237  return false;
238  }
239 
240  // Check the response's max-age header
241  if ($response->isFresh() === false) {
242  $maxStale = $reqc ? $reqc->getDirective('max-stale') : null;
243  if (null !== $maxStale) {
244  if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) {
245  return false;
246  }
247  } elseif ($resc && $resc->hasDirective('max-age')
248  && $responseAge > $resc->getDirective('max-age')
249  ) {
250  return false;
251  }
252  }
253 
254  if ($this->revalidation->shouldRevalidate($request, $response)) {
255  try {
256  return $this->revalidation->revalidate($request, $response);
257  } catch (CurlException $e) {
258  $request->getParams()->set('cache.hit', 'error');
259  return $this->canResponseSatisfyFailedRequest($request, $response);
260  }
261  }
262 
263  return true;
264  }
265 
274  public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)
275  {
276  $reqc = $request->getHeader('Cache-Control');
277  $resc = $response->getHeader('Cache-Control');
278  $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null;
279  $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null;
280 
281  if (!$requestStaleIfError && !$responseStaleIfError) {
282  return false;
283  }
284 
285  if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) {
286  return false;
287  }
288 
289  if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) {
290  return false;
291  }
292 
293  return true;
294  }
295 
301  public function purge($url)
302  {
303  // BC compatibility with previous version that accepted a Request object
304  $url = $url instanceof RequestInterface ? $url->getUrl() : $url;
305  $this->storage->purge($url);
306  }
307 
314  protected function addResponseHeaders(RequestInterface $request, Response $response)
315  {
316  $params = $request->getParams();
317  $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
318 
319  $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache';
320  if ($header = $response->getHeader('X-Cache-Lookup')) {
321  // Don't add duplicates
322  $values = $header->toArray();
323  $values[] = $lookup;
324  $response->setHeader('X-Cache-Lookup', array_unique($values));
325  } else {
326  $response->setHeader('X-Cache-Lookup', $lookup);
327  }
328 
329  if ($params['cache.hit'] === true) {
330  $xcache = 'HIT from GuzzleCache';
331  } elseif ($params['cache.hit'] == 'error') {
332  $xcache = 'HIT_ERROR from GuzzleCache';
333  } else {
334  $xcache = 'MISS from GuzzleCache';
335  }
336 
337  if ($header = $response->getHeader('X-Cache')) {
338  // Don't add duplicates
339  $values = $header->toArray();
340  $values[] = $xcache;
341  $response->setHeader('X-Cache', array_unique($values));
342  } else {
343  $response->setHeader('X-Cache', $xcache);
344  }
345 
346  if ($response->isFresh() === false) {
347  $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION));
348  if ($params['cache.hit'] === 'error') {
349  $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION));
350  }
351  }
352  }
353 }
Guzzle\Cache\CacheAdapterInterface
Definition: CacheAdapterInterface.php:12
Guzzle\Http\Message\RequestInterface\getProtocolVersion
getProtocolVersion()
Guzzle\Plugin\Cache\CachePlugin\__construct
__construct($options=null)
Definition: CachePlugin.php:63
Guzzle\Http\Exception\CurlException
Definition: CurlException.php:10
Guzzle\Plugin\Cache\CachePlugin\onRequestBeforeSend
onRequestBeforeSend(Event $event)
Definition: CachePlugin.php:115
Guzzle\Http\Message\RequestInterface
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php:16
Guzzle\Plugin\Cache\CachePlugin\onRequestError
onRequestError(Event $event)
Definition: CachePlugin.php:179
Symfony\Component\EventDispatcher\EventSubscriberInterface
Definition: lib/vendor/symfony/event-dispatcher/EventSubscriberInterface.php:25
Guzzle\Plugin\Cache\CachePlugin\purge
purge($url)
Definition: CachePlugin.php:313
Guzzle\Plugin\Cache
Definition: CacheKeyProviderInterface.php:3
Guzzle\Plugin\Cache\DefaultCanCacheStrategy
Definition: DefaultCanCacheStrategy.php:11
Guzzle\Plugin\Cache\CachePlugin\canResponseSatisfyRequest
canResponseSatisfyRequest(RequestInterface $request, Response $response)
Definition: CachePlugin.php:240
Guzzle\Http\Message\Response
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Response.php:17
Guzzle\Plugin\Cache\CachePlugin\addResponseHeaders
addResponseHeaders(RequestInterface $request, Response $response)
Definition: CachePlugin.php:326
Guzzle\Plugin\Cache\CachePlugin\canResponseSatisfyFailedRequest
canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)
Definition: CachePlugin.php:286
Guzzle\Plugin\Cache\DefaultCacheStorage
Definition: DefaultCacheStorage.php:16
Guzzle\Cache\DoctrineCacheAdapter
Definition: DoctrineCacheAdapter.php:12
Guzzle\Common\Version\VERSION
const VERSION
Definition: Version.php:10
Guzzle\Common\Event
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php:10
Guzzle\Cache\CacheAdapterFactory\fromCache
static fromCache($cache)
Definition: CacheAdapterFactory.php:25
Guzzle\Common\Exception\InvalidArgumentException
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php:5
Guzzle\Common\Version
Definition: Version.php:8
Guzzle\Plugin\Cache\CachePlugin\getSubscribedEvents
static getSubscribedEvents()
Definition: CachePlugin.php:100
Guzzle\Plugin\Cache\CachePlugin\$autoPurge
$autoPurge
Definition: CachePlugin.php:51
Guzzle\Cache\CacheAdapterFactory
Definition: CacheAdapterFactory.php:15
Guzzle\Plugin\Cache\CachePlugin\$storage
$storage
Definition: CachePlugin.php:45
Guzzle\Http\Message\MessageInterface\getHeader
getHeader($header)
Guzzle\Plugin\Cache\CallbackCanCacheStrategy
Definition: CallbackCanCacheStrategy.php:12
Guzzle\Plugin\Cache\DefaultRevalidation
Definition: DefaultRevalidation.php:12
Guzzle\Http\Message\MessageInterface\getParams
getParams()
Guzzle\Plugin\Cache\CachePlugin\onRequestException
onRequestException(Event $event)
Definition: CachePlugin.php:209
Symfony\Component\EventDispatcher\Event\stopPropagation
stopPropagation()
Definition: lib/vendor/symfony/event-dispatcher/Event.php:73
Guzzle\Plugin\Cache\CacheStorageInterface
Definition: CacheStorageInterface.php:11
Guzzle\Plugin\Cache\CachePlugin\$canCache
$canCache
Definition: CachePlugin.php:39
Guzzle\Plugin\Cache\CachePlugin\onRequestSent
onRequestSent(Event $event)
Definition: CachePlugin.php:159
Guzzle\Plugin\Cache\CachePlugin
Definition: CachePlugin.php:27
Guzzle\Plugin\Cache\CachePlugin\$revalidation
$revalidation
Definition: CachePlugin.php:33