1 <?php 2 3 namespace Kevinrob\GuzzleCache; 4 5 use GuzzleHttp\Psr7\PumpStream; 6 use Psr\Http\Message\RequestInterface; 7 use Psr\Http\Message\ResponseInterface; 8 9 class CacheEntry 10 { 11 /** 12 * @var RequestInterface 13 */ 14 protected $request; 15 16 /** 17 * @var ResponseInterface 18 */ 19 protected $response; 20 21 /** 22 * @var \DateTime 23 */ 24 protected $staleAt; 25 26 /** 27 * @var \DateTime 28 */ 29 protected $staleIfErrorTo; 30 31 /** 32 * @var \DateTime 33 */ 34 protected $staleWhileRevalidateTo; 35 36 /** 37 * @var \DateTime 38 */ 39 protected $dateCreated; 40 41 /** 42 * Cached timestamp of staleAt variable. 43 * 44 * @var int 45 */ 46 protected $timestampStale; 47 48 /** 49 * @param RequestInterface $request 50 * @param ResponseInterface $response 51 * @param \DateTime $staleAt 52 * @param \DateTime|null $staleIfErrorTo if null, detected with the headers (RFC 5861) 53 * @param \DateTime|null $staleWhileRevalidateTo 54 */ 55 public function __construct( 56 RequestInterface $request, 57 ResponseInterface $response, 58 \DateTime $staleAt, 59 \DateTime $staleIfErrorTo = null, 60 \DateTime $staleWhileRevalidateTo = null 61 ) { 62 $this->dateCreated = new \DateTime(); 63 64 $this->request = $request; 65 $this->response = $response; 66 $this->staleAt = $staleAt; 67 68 $values = new KeyValueHttpHeader($response->getHeader('Cache-Control')); 69 70 if ($staleIfErrorTo === null && $values->has('stale-if-error')) { 71 $this->staleIfErrorTo = (new \DateTime( 72 '@'.($this->staleAt->getTimestamp() + (int) $values->get('stale-if-error')) 73 )); 74 } else { 75 $this->staleIfErrorTo = $staleIfErrorTo; 76 } 77 78 if ($staleWhileRevalidateTo === null && $values->has('stale-while-revalidate')) { 79 $this->staleWhileRevalidateTo = new \DateTime( 80 '@'.($this->staleAt->getTimestamp() + (int) $values->get('stale-while-revalidate')) 81 ); 82 } else { 83 $this->staleWhileRevalidateTo = $staleWhileRevalidateTo; 84 } 85 } 86 87 /** 88 * @return ResponseInterface 89 */ 90 public function getResponse() 91 { 92 return $this->response 93 ->withHeader('Age', $this->getAge()); 94 } 95 96 /** 97 * @return ResponseInterface 98 */ 99 public function getOriginalResponse() 100 { 101 return $this->response; 102 } 103 104 /** 105 * @return RequestInterface 106 */ 107 public function getOriginalRequest() 108 { 109 return $this->request; 110 } 111 112 /** 113 * @param RequestInterface $request 114 * @return bool 115 */ 116 public function isVaryEquals(RequestInterface $request) 117 { 118 if ($this->response->hasHeader('Vary')) { 119 if ($this->request === null) { 120 return false; 121 } 122 123 foreach ($this->getVaryHeaders() as $key => $value) { 124 if (!$this->request->hasHeader($key) 125 && !$request->hasHeader($key) 126 ) { 127 // Absent from both 128 continue; 129 } elseif ($this->request->getHeaderLine($key) 130 == $request->getHeaderLine($key) 131 ) { 132 // Same content 133 continue; 134 } 135 136 return false; 137 } 138 } 139 140 return true; 141 } 142 143 /** 144 * Get the vary headers that should be honoured by the cache. 145 * 146 * @return KeyValueHttpHeader 147 */ 148 public function getVaryHeaders() 149 { 150 return new KeyValueHttpHeader($this->response->getHeader('Vary')); 151 } 152 153 /** 154 * @return \DateTime 155 */ 156 public function getStaleAt() 157 { 158 return $this->staleAt; 159 } 160 161 /** 162 * @return bool 163 */ 164 public function isFresh() 165 { 166 return !$this->isStale(); 167 } 168 169 /** 170 * @return bool 171 */ 172 public function isStale() 173 { 174 return $this->getStaleAge() > 0; 175 } 176 177 /** 178 * @return int positive value equal staled 179 */ 180 public function getStaleAge() 181 { 182 // This object is immutable 183 if ($this->timestampStale === null) { 184 $this->timestampStale = $this->staleAt->getTimestamp(); 185 } 186 187 return time() - $this->timestampStale; 188 } 189 190 /** 191 * @return bool 192 */ 193 public function serveStaleIfError() 194 { 195 return $this->staleIfErrorTo !== null 196 && $this->staleIfErrorTo->getTimestamp() >= (new \DateTime())->getTimestamp(); 197 } 198 199 /** 200 * @return bool 201 */ 202 public function staleWhileValidate() 203 { 204 return $this->staleWhileRevalidateTo !== null 205 && $this->staleWhileRevalidateTo->getTimestamp() >= (new \DateTime())->getTimestamp(); 206 } 207 208 /** 209 * @return bool 210 */ 211 public function hasValidationInformation() 212 { 213 return $this->response->hasHeader('Etag') || $this->response->hasHeader('Last-Modified'); 214 } 215 216 /** 217 * Time in seconds how long the entry should be kept in the cache 218 * 219 * This will not give the time (in seconds) that the response will still be fresh for 220 * from the HTTP point of view, but an upper bound on how long it is necessary and 221 * reasonable to keep the response in a cache (to re-use it or re-validate it later on). 222 * 223 * @return int TTL in seconds (0 = infinite) 224 */ 225 public function getTTL() 226 { 227 if ($this->hasValidationInformation()) { 228 // No TTL if we have a way to re-validate the cache 229 return 0; 230 } 231 232 $ttl = 0; 233 234 // Keep it when stale if error 235 if ($this->staleIfErrorTo !== null) { 236 $ttl = max($ttl, $this->staleIfErrorTo->getTimestamp() - time()); 237 } 238 239 // Keep it when stale-while-revalidate 240 if ($this->staleWhileRevalidateTo !== null) { 241 $ttl = max($ttl, $this->staleWhileRevalidateTo->getTimestamp() - time()); 242 } 243 244 // Keep it until it become stale 245 $ttl = max($ttl, $this->staleAt->getTimestamp() - time()); 246 247 // Don't return 0, it's reserved for infinite TTL 248 return $ttl !== 0 ? (int) $ttl : -1; 249 } 250 251 /** 252 * @return int Age in seconds 253 */ 254 public function getAge() 255 { 256 return time() - $this->dateCreated->getTimestamp(); 257 } 258 259 public function __sleep() 260 { 261 // Stream/Resource can't be serialized... So we copy the content into an implementation of `Psr\Http\Message\StreamInterface` 262 if ($this->response !== null) { 263 $responseBody = (string)$this->response->getBody(); 264 $this->response = $this->response->withBody( 265 new PumpStream( 266 new BodyStore($responseBody), 267 [ 268 'size' => mb_strlen($responseBody), 269 ] 270 ) 271 ); 272 } 273 274 $requestBody = (string)$this->request->getBody(); 275 $this->request = $this->request->withBody( 276 new PumpStream( 277 new BodyStore($requestBody), 278 [ 279 'size' => mb_strlen($requestBody) 280 ] 281 ) 282 ); 283 284 return array_keys(get_object_vars($this)); 285 } 286 287 public function __wakeup() 288 { 289 // We re-create the stream of the response 290 if ($this->response !== null) { 291 $this->response = $this->response 292 ->withBody( 293 \GuzzleHttp\Psr7\Utils::streamFor((string) $this->response->getBody()) 294 ); 295 } 296 $this->request = $this->request 297 ->withBody( 298 \GuzzleHttp\Psr7\Utils::streamFor((string) $this->request->getBody()) 299 ); 300 } 301 302 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body