Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\HttpFoundation;
|
Chris@0
|
13
|
Chris@0
|
14 /**
|
Chris@0
|
15 * Response represents an HTTP response.
|
Chris@0
|
16 *
|
Chris@0
|
17 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
18 */
|
Chris@0
|
19 class Response
|
Chris@0
|
20 {
|
Chris@0
|
21 const HTTP_CONTINUE = 100;
|
Chris@0
|
22 const HTTP_SWITCHING_PROTOCOLS = 101;
|
Chris@0
|
23 const HTTP_PROCESSING = 102; // RFC2518
|
Chris@0
|
24 const HTTP_OK = 200;
|
Chris@0
|
25 const HTTP_CREATED = 201;
|
Chris@0
|
26 const HTTP_ACCEPTED = 202;
|
Chris@0
|
27 const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
|
Chris@0
|
28 const HTTP_NO_CONTENT = 204;
|
Chris@0
|
29 const HTTP_RESET_CONTENT = 205;
|
Chris@0
|
30 const HTTP_PARTIAL_CONTENT = 206;
|
Chris@0
|
31 const HTTP_MULTI_STATUS = 207; // RFC4918
|
Chris@0
|
32 const HTTP_ALREADY_REPORTED = 208; // RFC5842
|
Chris@0
|
33 const HTTP_IM_USED = 226; // RFC3229
|
Chris@0
|
34 const HTTP_MULTIPLE_CHOICES = 300;
|
Chris@0
|
35 const HTTP_MOVED_PERMANENTLY = 301;
|
Chris@0
|
36 const HTTP_FOUND = 302;
|
Chris@0
|
37 const HTTP_SEE_OTHER = 303;
|
Chris@0
|
38 const HTTP_NOT_MODIFIED = 304;
|
Chris@0
|
39 const HTTP_USE_PROXY = 305;
|
Chris@0
|
40 const HTTP_RESERVED = 306;
|
Chris@0
|
41 const HTTP_TEMPORARY_REDIRECT = 307;
|
Chris@0
|
42 const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
|
Chris@0
|
43 const HTTP_BAD_REQUEST = 400;
|
Chris@0
|
44 const HTTP_UNAUTHORIZED = 401;
|
Chris@0
|
45 const HTTP_PAYMENT_REQUIRED = 402;
|
Chris@0
|
46 const HTTP_FORBIDDEN = 403;
|
Chris@0
|
47 const HTTP_NOT_FOUND = 404;
|
Chris@0
|
48 const HTTP_METHOD_NOT_ALLOWED = 405;
|
Chris@0
|
49 const HTTP_NOT_ACCEPTABLE = 406;
|
Chris@0
|
50 const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
|
Chris@0
|
51 const HTTP_REQUEST_TIMEOUT = 408;
|
Chris@0
|
52 const HTTP_CONFLICT = 409;
|
Chris@0
|
53 const HTTP_GONE = 410;
|
Chris@0
|
54 const HTTP_LENGTH_REQUIRED = 411;
|
Chris@0
|
55 const HTTP_PRECONDITION_FAILED = 412;
|
Chris@0
|
56 const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
|
Chris@0
|
57 const HTTP_REQUEST_URI_TOO_LONG = 414;
|
Chris@0
|
58 const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
|
Chris@0
|
59 const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
Chris@0
|
60 const HTTP_EXPECTATION_FAILED = 417;
|
Chris@0
|
61 const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
|
Chris@0
|
62 const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
|
Chris@0
|
63 const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
|
Chris@0
|
64 const HTTP_LOCKED = 423; // RFC4918
|
Chris@0
|
65 const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
|
Chris@0
|
66 const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
|
Chris@0
|
67 const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
|
Chris@0
|
68 const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
|
Chris@0
|
69 const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
|
Chris@0
|
70 const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
|
Chris@0
|
71 const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
|
Chris@0
|
72 const HTTP_INTERNAL_SERVER_ERROR = 500;
|
Chris@0
|
73 const HTTP_NOT_IMPLEMENTED = 501;
|
Chris@0
|
74 const HTTP_BAD_GATEWAY = 502;
|
Chris@0
|
75 const HTTP_SERVICE_UNAVAILABLE = 503;
|
Chris@0
|
76 const HTTP_GATEWAY_TIMEOUT = 504;
|
Chris@0
|
77 const HTTP_VERSION_NOT_SUPPORTED = 505;
|
Chris@0
|
78 const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
|
Chris@0
|
79 const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
|
Chris@0
|
80 const HTTP_LOOP_DETECTED = 508; // RFC5842
|
Chris@0
|
81 const HTTP_NOT_EXTENDED = 510; // RFC2774
|
Chris@0
|
82 const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
|
Chris@0
|
83
|
Chris@0
|
84 /**
|
Chris@0
|
85 * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
|
Chris@0
|
86 */
|
Chris@0
|
87 public $headers;
|
Chris@0
|
88
|
Chris@0
|
89 /**
|
Chris@0
|
90 * @var string
|
Chris@0
|
91 */
|
Chris@0
|
92 protected $content;
|
Chris@0
|
93
|
Chris@0
|
94 /**
|
Chris@0
|
95 * @var string
|
Chris@0
|
96 */
|
Chris@0
|
97 protected $version;
|
Chris@0
|
98
|
Chris@0
|
99 /**
|
Chris@0
|
100 * @var int
|
Chris@0
|
101 */
|
Chris@0
|
102 protected $statusCode;
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@0
|
105 * @var string
|
Chris@0
|
106 */
|
Chris@0
|
107 protected $statusText;
|
Chris@0
|
108
|
Chris@0
|
109 /**
|
Chris@0
|
110 * @var string
|
Chris@0
|
111 */
|
Chris@0
|
112 protected $charset;
|
Chris@0
|
113
|
Chris@0
|
114 /**
|
Chris@0
|
115 * Status codes translation table.
|
Chris@0
|
116 *
|
Chris@0
|
117 * The list of codes is complete according to the
|
Chris@0
|
118 * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
|
Chris@0
|
119 * (last updated 2016-03-01).
|
Chris@0
|
120 *
|
Chris@0
|
121 * Unless otherwise noted, the status code is defined in RFC2616.
|
Chris@0
|
122 *
|
Chris@0
|
123 * @var array
|
Chris@0
|
124 */
|
Chris@0
|
125 public static $statusTexts = array(
|
Chris@0
|
126 100 => 'Continue',
|
Chris@0
|
127 101 => 'Switching Protocols',
|
Chris@0
|
128 102 => 'Processing', // RFC2518
|
Chris@14
|
129 103 => 'Early Hints',
|
Chris@0
|
130 200 => 'OK',
|
Chris@0
|
131 201 => 'Created',
|
Chris@0
|
132 202 => 'Accepted',
|
Chris@0
|
133 203 => 'Non-Authoritative Information',
|
Chris@0
|
134 204 => 'No Content',
|
Chris@0
|
135 205 => 'Reset Content',
|
Chris@0
|
136 206 => 'Partial Content',
|
Chris@0
|
137 207 => 'Multi-Status', // RFC4918
|
Chris@0
|
138 208 => 'Already Reported', // RFC5842
|
Chris@0
|
139 226 => 'IM Used', // RFC3229
|
Chris@0
|
140 300 => 'Multiple Choices',
|
Chris@0
|
141 301 => 'Moved Permanently',
|
Chris@0
|
142 302 => 'Found',
|
Chris@0
|
143 303 => 'See Other',
|
Chris@0
|
144 304 => 'Not Modified',
|
Chris@0
|
145 305 => 'Use Proxy',
|
Chris@0
|
146 307 => 'Temporary Redirect',
|
Chris@0
|
147 308 => 'Permanent Redirect', // RFC7238
|
Chris@0
|
148 400 => 'Bad Request',
|
Chris@0
|
149 401 => 'Unauthorized',
|
Chris@0
|
150 402 => 'Payment Required',
|
Chris@0
|
151 403 => 'Forbidden',
|
Chris@0
|
152 404 => 'Not Found',
|
Chris@0
|
153 405 => 'Method Not Allowed',
|
Chris@0
|
154 406 => 'Not Acceptable',
|
Chris@0
|
155 407 => 'Proxy Authentication Required',
|
Chris@0
|
156 408 => 'Request Timeout',
|
Chris@0
|
157 409 => 'Conflict',
|
Chris@0
|
158 410 => 'Gone',
|
Chris@0
|
159 411 => 'Length Required',
|
Chris@0
|
160 412 => 'Precondition Failed',
|
Chris@0
|
161 413 => 'Payload Too Large',
|
Chris@0
|
162 414 => 'URI Too Long',
|
Chris@0
|
163 415 => 'Unsupported Media Type',
|
Chris@0
|
164 416 => 'Range Not Satisfiable',
|
Chris@0
|
165 417 => 'Expectation Failed',
|
Chris@0
|
166 418 => 'I\'m a teapot', // RFC2324
|
Chris@0
|
167 421 => 'Misdirected Request', // RFC7540
|
Chris@0
|
168 422 => 'Unprocessable Entity', // RFC4918
|
Chris@0
|
169 423 => 'Locked', // RFC4918
|
Chris@0
|
170 424 => 'Failed Dependency', // RFC4918
|
Chris@0
|
171 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817
|
Chris@0
|
172 426 => 'Upgrade Required', // RFC2817
|
Chris@0
|
173 428 => 'Precondition Required', // RFC6585
|
Chris@0
|
174 429 => 'Too Many Requests', // RFC6585
|
Chris@0
|
175 431 => 'Request Header Fields Too Large', // RFC6585
|
Chris@0
|
176 451 => 'Unavailable For Legal Reasons', // RFC7725
|
Chris@0
|
177 500 => 'Internal Server Error',
|
Chris@0
|
178 501 => 'Not Implemented',
|
Chris@0
|
179 502 => 'Bad Gateway',
|
Chris@0
|
180 503 => 'Service Unavailable',
|
Chris@0
|
181 504 => 'Gateway Timeout',
|
Chris@0
|
182 505 => 'HTTP Version Not Supported',
|
Chris@0
|
183 506 => 'Variant Also Negotiates', // RFC2295
|
Chris@0
|
184 507 => 'Insufficient Storage', // RFC4918
|
Chris@0
|
185 508 => 'Loop Detected', // RFC5842
|
Chris@0
|
186 510 => 'Not Extended', // RFC2774
|
Chris@0
|
187 511 => 'Network Authentication Required', // RFC6585
|
Chris@0
|
188 );
|
Chris@0
|
189
|
Chris@0
|
190 /**
|
Chris@0
|
191 * @param mixed $content The response content, see setContent()
|
Chris@0
|
192 * @param int $status The response status code
|
Chris@0
|
193 * @param array $headers An array of response headers
|
Chris@0
|
194 *
|
Chris@0
|
195 * @throws \InvalidArgumentException When the HTTP status code is not valid
|
Chris@0
|
196 */
|
Chris@0
|
197 public function __construct($content = '', $status = 200, $headers = array())
|
Chris@0
|
198 {
|
Chris@0
|
199 $this->headers = new ResponseHeaderBag($headers);
|
Chris@0
|
200 $this->setContent($content);
|
Chris@0
|
201 $this->setStatusCode($status);
|
Chris@0
|
202 $this->setProtocolVersion('1.0');
|
Chris@0
|
203 }
|
Chris@0
|
204
|
Chris@0
|
205 /**
|
Chris@0
|
206 * Factory method for chainability.
|
Chris@0
|
207 *
|
Chris@0
|
208 * Example:
|
Chris@0
|
209 *
|
Chris@0
|
210 * return Response::create($body, 200)
|
Chris@0
|
211 * ->setSharedMaxAge(300);
|
Chris@0
|
212 *
|
Chris@0
|
213 * @param mixed $content The response content, see setContent()
|
Chris@0
|
214 * @param int $status The response status code
|
Chris@0
|
215 * @param array $headers An array of response headers
|
Chris@0
|
216 *
|
Chris@0
|
217 * @return static
|
Chris@0
|
218 */
|
Chris@0
|
219 public static function create($content = '', $status = 200, $headers = array())
|
Chris@0
|
220 {
|
Chris@0
|
221 return new static($content, $status, $headers);
|
Chris@0
|
222 }
|
Chris@0
|
223
|
Chris@0
|
224 /**
|
Chris@0
|
225 * Returns the Response as an HTTP string.
|
Chris@0
|
226 *
|
Chris@0
|
227 * The string representation of the Response is the same as the
|
Chris@0
|
228 * one that will be sent to the client only if the prepare() method
|
Chris@0
|
229 * has been called before.
|
Chris@0
|
230 *
|
Chris@0
|
231 * @return string The Response as an HTTP string
|
Chris@0
|
232 *
|
Chris@0
|
233 * @see prepare()
|
Chris@0
|
234 */
|
Chris@0
|
235 public function __toString()
|
Chris@0
|
236 {
|
Chris@0
|
237 return
|
Chris@0
|
238 sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
|
Chris@0
|
239 $this->headers."\r\n".
|
Chris@0
|
240 $this->getContent();
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 /**
|
Chris@0
|
244 * Clones the current Response instance.
|
Chris@0
|
245 */
|
Chris@0
|
246 public function __clone()
|
Chris@0
|
247 {
|
Chris@0
|
248 $this->headers = clone $this->headers;
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 /**
|
Chris@0
|
252 * Prepares the Response before it is sent to the client.
|
Chris@0
|
253 *
|
Chris@0
|
254 * This method tweaks the Response to ensure that it is
|
Chris@0
|
255 * compliant with RFC 2616. Most of the changes are based on
|
Chris@0
|
256 * the Request that is "associated" with this Response.
|
Chris@0
|
257 *
|
Chris@0
|
258 * @return $this
|
Chris@0
|
259 */
|
Chris@0
|
260 public function prepare(Request $request)
|
Chris@0
|
261 {
|
Chris@0
|
262 $headers = $this->headers;
|
Chris@0
|
263
|
Chris@0
|
264 if ($this->isInformational() || $this->isEmpty()) {
|
Chris@0
|
265 $this->setContent(null);
|
Chris@0
|
266 $headers->remove('Content-Type');
|
Chris@0
|
267 $headers->remove('Content-Length');
|
Chris@0
|
268 } else {
|
Chris@0
|
269 // Content-type based on the Request
|
Chris@0
|
270 if (!$headers->has('Content-Type')) {
|
Chris@0
|
271 $format = $request->getRequestFormat();
|
Chris@0
|
272 if (null !== $format && $mimeType = $request->getMimeType($format)) {
|
Chris@0
|
273 $headers->set('Content-Type', $mimeType);
|
Chris@0
|
274 }
|
Chris@0
|
275 }
|
Chris@0
|
276
|
Chris@0
|
277 // Fix Content-Type
|
Chris@0
|
278 $charset = $this->charset ?: 'UTF-8';
|
Chris@0
|
279 if (!$headers->has('Content-Type')) {
|
Chris@0
|
280 $headers->set('Content-Type', 'text/html; charset='.$charset);
|
Chris@0
|
281 } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
|
Chris@0
|
282 // add the charset
|
Chris@0
|
283 $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
|
Chris@0
|
284 }
|
Chris@0
|
285
|
Chris@0
|
286 // Fix Content-Length
|
Chris@0
|
287 if ($headers->has('Transfer-Encoding')) {
|
Chris@0
|
288 $headers->remove('Content-Length');
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 if ($request->isMethod('HEAD')) {
|
Chris@0
|
292 // cf. RFC2616 14.13
|
Chris@0
|
293 $length = $headers->get('Content-Length');
|
Chris@0
|
294 $this->setContent(null);
|
Chris@0
|
295 if ($length) {
|
Chris@0
|
296 $headers->set('Content-Length', $length);
|
Chris@0
|
297 }
|
Chris@0
|
298 }
|
Chris@0
|
299 }
|
Chris@0
|
300
|
Chris@0
|
301 // Fix protocol
|
Chris@0
|
302 if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
|
Chris@0
|
303 $this->setProtocolVersion('1.1');
|
Chris@0
|
304 }
|
Chris@0
|
305
|
Chris@0
|
306 // Check if we need to send extra expire info headers
|
Chris@0
|
307 if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
|
Chris@0
|
308 $this->headers->set('pragma', 'no-cache');
|
Chris@0
|
309 $this->headers->set('expires', -1);
|
Chris@0
|
310 }
|
Chris@0
|
311
|
Chris@0
|
312 $this->ensureIEOverSSLCompatibility($request);
|
Chris@0
|
313
|
Chris@0
|
314 return $this;
|
Chris@0
|
315 }
|
Chris@0
|
316
|
Chris@0
|
317 /**
|
Chris@0
|
318 * Sends HTTP headers.
|
Chris@0
|
319 *
|
Chris@0
|
320 * @return $this
|
Chris@0
|
321 */
|
Chris@0
|
322 public function sendHeaders()
|
Chris@0
|
323 {
|
Chris@0
|
324 // headers have already been sent by the developer
|
Chris@0
|
325 if (headers_sent()) {
|
Chris@0
|
326 return $this;
|
Chris@0
|
327 }
|
Chris@0
|
328
|
Chris@0
|
329 // headers
|
Chris@14
|
330 foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
|
Chris@0
|
331 foreach ($values as $value) {
|
Chris@0
|
332 header($name.': '.$value, false, $this->statusCode);
|
Chris@0
|
333 }
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 // status
|
Chris@0
|
337 header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
|
Chris@0
|
338
|
Chris@0
|
339 // cookies
|
Chris@0
|
340 foreach ($this->headers->getCookies() as $cookie) {
|
Chris@0
|
341 if ($cookie->isRaw()) {
|
Chris@0
|
342 setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
|
Chris@0
|
343 } else {
|
Chris@0
|
344 setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
|
Chris@0
|
345 }
|
Chris@0
|
346 }
|
Chris@0
|
347
|
Chris@0
|
348 return $this;
|
Chris@0
|
349 }
|
Chris@0
|
350
|
Chris@0
|
351 /**
|
Chris@0
|
352 * Sends content for the current web response.
|
Chris@0
|
353 *
|
Chris@0
|
354 * @return $this
|
Chris@0
|
355 */
|
Chris@0
|
356 public function sendContent()
|
Chris@0
|
357 {
|
Chris@0
|
358 echo $this->content;
|
Chris@0
|
359
|
Chris@0
|
360 return $this;
|
Chris@0
|
361 }
|
Chris@0
|
362
|
Chris@0
|
363 /**
|
Chris@0
|
364 * Sends HTTP headers and content.
|
Chris@0
|
365 *
|
Chris@0
|
366 * @return $this
|
Chris@0
|
367 */
|
Chris@0
|
368 public function send()
|
Chris@0
|
369 {
|
Chris@0
|
370 $this->sendHeaders();
|
Chris@0
|
371 $this->sendContent();
|
Chris@0
|
372
|
Chris@0
|
373 if (function_exists('fastcgi_finish_request')) {
|
Chris@0
|
374 fastcgi_finish_request();
|
Chris@14
|
375 } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
|
Chris@0
|
376 static::closeOutputBuffers(0, true);
|
Chris@0
|
377 }
|
Chris@0
|
378
|
Chris@0
|
379 return $this;
|
Chris@0
|
380 }
|
Chris@0
|
381
|
Chris@0
|
382 /**
|
Chris@0
|
383 * Sets the response content.
|
Chris@0
|
384 *
|
Chris@0
|
385 * Valid types are strings, numbers, null, and objects that implement a __toString() method.
|
Chris@0
|
386 *
|
Chris@0
|
387 * @param mixed $content Content that can be cast to string
|
Chris@0
|
388 *
|
Chris@0
|
389 * @return $this
|
Chris@0
|
390 *
|
Chris@0
|
391 * @throws \UnexpectedValueException
|
Chris@0
|
392 */
|
Chris@0
|
393 public function setContent($content)
|
Chris@0
|
394 {
|
Chris@0
|
395 if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
|
Chris@0
|
396 throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content)));
|
Chris@0
|
397 }
|
Chris@0
|
398
|
Chris@0
|
399 $this->content = (string) $content;
|
Chris@0
|
400
|
Chris@0
|
401 return $this;
|
Chris@0
|
402 }
|
Chris@0
|
403
|
Chris@0
|
404 /**
|
Chris@0
|
405 * Gets the current response content.
|
Chris@0
|
406 *
|
Chris@0
|
407 * @return string Content
|
Chris@0
|
408 */
|
Chris@0
|
409 public function getContent()
|
Chris@0
|
410 {
|
Chris@0
|
411 return $this->content;
|
Chris@0
|
412 }
|
Chris@0
|
413
|
Chris@0
|
414 /**
|
Chris@0
|
415 * Sets the HTTP protocol version (1.0 or 1.1).
|
Chris@0
|
416 *
|
Chris@0
|
417 * @param string $version The HTTP protocol version
|
Chris@0
|
418 *
|
Chris@0
|
419 * @return $this
|
Chris@14
|
420 *
|
Chris@14
|
421 * @final since version 3.2
|
Chris@0
|
422 */
|
Chris@0
|
423 public function setProtocolVersion($version)
|
Chris@0
|
424 {
|
Chris@0
|
425 $this->version = $version;
|
Chris@0
|
426
|
Chris@0
|
427 return $this;
|
Chris@0
|
428 }
|
Chris@0
|
429
|
Chris@0
|
430 /**
|
Chris@0
|
431 * Gets the HTTP protocol version.
|
Chris@0
|
432 *
|
Chris@0
|
433 * @return string The HTTP protocol version
|
Chris@14
|
434 *
|
Chris@14
|
435 * @final since version 3.2
|
Chris@0
|
436 */
|
Chris@0
|
437 public function getProtocolVersion()
|
Chris@0
|
438 {
|
Chris@0
|
439 return $this->version;
|
Chris@0
|
440 }
|
Chris@0
|
441
|
Chris@0
|
442 /**
|
Chris@0
|
443 * Sets the response status code.
|
Chris@0
|
444 *
|
Chris@14
|
445 * If the status text is null it will be automatically populated for the known
|
Chris@14
|
446 * status codes and left empty otherwise.
|
Chris@14
|
447 *
|
Chris@0
|
448 * @param int $code HTTP status code
|
Chris@0
|
449 * @param mixed $text HTTP status text
|
Chris@0
|
450 *
|
Chris@0
|
451 * @return $this
|
Chris@0
|
452 *
|
Chris@0
|
453 * @throws \InvalidArgumentException When the HTTP status code is not valid
|
Chris@14
|
454 *
|
Chris@14
|
455 * @final since version 3.2
|
Chris@0
|
456 */
|
Chris@0
|
457 public function setStatusCode($code, $text = null)
|
Chris@0
|
458 {
|
Chris@0
|
459 $this->statusCode = $code = (int) $code;
|
Chris@0
|
460 if ($this->isInvalid()) {
|
Chris@0
|
461 throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
|
Chris@0
|
462 }
|
Chris@0
|
463
|
Chris@0
|
464 if (null === $text) {
|
Chris@0
|
465 $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
|
Chris@0
|
466
|
Chris@0
|
467 return $this;
|
Chris@0
|
468 }
|
Chris@0
|
469
|
Chris@0
|
470 if (false === $text) {
|
Chris@0
|
471 $this->statusText = '';
|
Chris@0
|
472
|
Chris@0
|
473 return $this;
|
Chris@0
|
474 }
|
Chris@0
|
475
|
Chris@0
|
476 $this->statusText = $text;
|
Chris@0
|
477
|
Chris@0
|
478 return $this;
|
Chris@0
|
479 }
|
Chris@0
|
480
|
Chris@0
|
481 /**
|
Chris@0
|
482 * Retrieves the status code for the current web response.
|
Chris@0
|
483 *
|
Chris@0
|
484 * @return int Status code
|
Chris@14
|
485 *
|
Chris@14
|
486 * @final since version 3.2
|
Chris@0
|
487 */
|
Chris@0
|
488 public function getStatusCode()
|
Chris@0
|
489 {
|
Chris@0
|
490 return $this->statusCode;
|
Chris@0
|
491 }
|
Chris@0
|
492
|
Chris@0
|
493 /**
|
Chris@0
|
494 * Sets the response charset.
|
Chris@0
|
495 *
|
Chris@0
|
496 * @param string $charset Character set
|
Chris@0
|
497 *
|
Chris@0
|
498 * @return $this
|
Chris@14
|
499 *
|
Chris@14
|
500 * @final since version 3.2
|
Chris@0
|
501 */
|
Chris@0
|
502 public function setCharset($charset)
|
Chris@0
|
503 {
|
Chris@0
|
504 $this->charset = $charset;
|
Chris@0
|
505
|
Chris@0
|
506 return $this;
|
Chris@0
|
507 }
|
Chris@0
|
508
|
Chris@0
|
509 /**
|
Chris@0
|
510 * Retrieves the response charset.
|
Chris@0
|
511 *
|
Chris@0
|
512 * @return string Character set
|
Chris@14
|
513 *
|
Chris@14
|
514 * @final since version 3.2
|
Chris@0
|
515 */
|
Chris@0
|
516 public function getCharset()
|
Chris@0
|
517 {
|
Chris@0
|
518 return $this->charset;
|
Chris@0
|
519 }
|
Chris@0
|
520
|
Chris@0
|
521 /**
|
Chris@0
|
522 * Returns true if the response is worth caching under any circumstance.
|
Chris@0
|
523 *
|
Chris@0
|
524 * Responses marked "private" with an explicit Cache-Control directive are
|
Chris@0
|
525 * considered uncacheable.
|
Chris@0
|
526 *
|
Chris@0
|
527 * Responses with neither a freshness lifetime (Expires, max-age) nor cache
|
Chris@0
|
528 * validator (Last-Modified, ETag) are considered uncacheable.
|
Chris@0
|
529 *
|
Chris@0
|
530 * @return bool true if the response is worth caching, false otherwise
|
Chris@14
|
531 *
|
Chris@14
|
532 * @final since version 3.3
|
Chris@0
|
533 */
|
Chris@0
|
534 public function isCacheable()
|
Chris@0
|
535 {
|
Chris@0
|
536 if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
|
Chris@0
|
537 return false;
|
Chris@0
|
538 }
|
Chris@0
|
539
|
Chris@0
|
540 if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
|
Chris@0
|
541 return false;
|
Chris@0
|
542 }
|
Chris@0
|
543
|
Chris@0
|
544 return $this->isValidateable() || $this->isFresh();
|
Chris@0
|
545 }
|
Chris@0
|
546
|
Chris@0
|
547 /**
|
Chris@0
|
548 * Returns true if the response is "fresh".
|
Chris@0
|
549 *
|
Chris@0
|
550 * Fresh responses may be served from cache without any interaction with the
|
Chris@0
|
551 * origin. A response is considered fresh when it includes a Cache-Control/max-age
|
Chris@0
|
552 * indicator or Expires header and the calculated age is less than the freshness lifetime.
|
Chris@0
|
553 *
|
Chris@0
|
554 * @return bool true if the response is fresh, false otherwise
|
Chris@14
|
555 *
|
Chris@14
|
556 * @final since version 3.3
|
Chris@0
|
557 */
|
Chris@0
|
558 public function isFresh()
|
Chris@0
|
559 {
|
Chris@0
|
560 return $this->getTtl() > 0;
|
Chris@0
|
561 }
|
Chris@0
|
562
|
Chris@0
|
563 /**
|
Chris@0
|
564 * Returns true if the response includes headers that can be used to validate
|
Chris@0
|
565 * the response with the origin server using a conditional GET request.
|
Chris@0
|
566 *
|
Chris@0
|
567 * @return bool true if the response is validateable, false otherwise
|
Chris@14
|
568 *
|
Chris@14
|
569 * @final since version 3.3
|
Chris@0
|
570 */
|
Chris@0
|
571 public function isValidateable()
|
Chris@0
|
572 {
|
Chris@0
|
573 return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
|
Chris@0
|
574 }
|
Chris@0
|
575
|
Chris@0
|
576 /**
|
Chris@0
|
577 * Marks the response as "private".
|
Chris@0
|
578 *
|
Chris@0
|
579 * It makes the response ineligible for serving other clients.
|
Chris@0
|
580 *
|
Chris@0
|
581 * @return $this
|
Chris@14
|
582 *
|
Chris@14
|
583 * @final since version 3.2
|
Chris@0
|
584 */
|
Chris@0
|
585 public function setPrivate()
|
Chris@0
|
586 {
|
Chris@0
|
587 $this->headers->removeCacheControlDirective('public');
|
Chris@0
|
588 $this->headers->addCacheControlDirective('private');
|
Chris@0
|
589
|
Chris@0
|
590 return $this;
|
Chris@0
|
591 }
|
Chris@0
|
592
|
Chris@0
|
593 /**
|
Chris@0
|
594 * Marks the response as "public".
|
Chris@0
|
595 *
|
Chris@0
|
596 * It makes the response eligible for serving other clients.
|
Chris@0
|
597 *
|
Chris@0
|
598 * @return $this
|
Chris@14
|
599 *
|
Chris@14
|
600 * @final since version 3.2
|
Chris@0
|
601 */
|
Chris@0
|
602 public function setPublic()
|
Chris@0
|
603 {
|
Chris@0
|
604 $this->headers->addCacheControlDirective('public');
|
Chris@0
|
605 $this->headers->removeCacheControlDirective('private');
|
Chris@0
|
606
|
Chris@0
|
607 return $this;
|
Chris@0
|
608 }
|
Chris@0
|
609
|
Chris@0
|
610 /**
|
Chris@14
|
611 * Marks the response as "immutable".
|
Chris@14
|
612 *
|
Chris@14
|
613 * @param bool $immutable enables or disables the immutable directive
|
Chris@14
|
614 *
|
Chris@14
|
615 * @return $this
|
Chris@14
|
616 *
|
Chris@14
|
617 * @final
|
Chris@14
|
618 */
|
Chris@14
|
619 public function setImmutable($immutable = true)
|
Chris@14
|
620 {
|
Chris@14
|
621 if ($immutable) {
|
Chris@14
|
622 $this->headers->addCacheControlDirective('immutable');
|
Chris@14
|
623 } else {
|
Chris@14
|
624 $this->headers->removeCacheControlDirective('immutable');
|
Chris@14
|
625 }
|
Chris@14
|
626
|
Chris@14
|
627 return $this;
|
Chris@14
|
628 }
|
Chris@14
|
629
|
Chris@14
|
630 /**
|
Chris@14
|
631 * Returns true if the response is marked as "immutable".
|
Chris@14
|
632 *
|
Chris@14
|
633 * @return bool returns true if the response is marked as "immutable"; otherwise false
|
Chris@14
|
634 *
|
Chris@14
|
635 * @final
|
Chris@14
|
636 */
|
Chris@14
|
637 public function isImmutable()
|
Chris@14
|
638 {
|
Chris@14
|
639 return $this->headers->hasCacheControlDirective('immutable');
|
Chris@14
|
640 }
|
Chris@14
|
641
|
Chris@14
|
642 /**
|
Chris@0
|
643 * Returns true if the response must be revalidated by caches.
|
Chris@0
|
644 *
|
Chris@0
|
645 * This method indicates that the response must not be served stale by a
|
Chris@0
|
646 * cache in any circumstance without first revalidating with the origin.
|
Chris@0
|
647 * When present, the TTL of the response should not be overridden to be
|
Chris@0
|
648 * greater than the value provided by the origin.
|
Chris@0
|
649 *
|
Chris@0
|
650 * @return bool true if the response must be revalidated by a cache, false otherwise
|
Chris@14
|
651 *
|
Chris@14
|
652 * @final since version 3.3
|
Chris@0
|
653 */
|
Chris@0
|
654 public function mustRevalidate()
|
Chris@0
|
655 {
|
Chris@0
|
656 return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
|
Chris@0
|
657 }
|
Chris@0
|
658
|
Chris@0
|
659 /**
|
Chris@0
|
660 * Returns the Date header as a DateTime instance.
|
Chris@0
|
661 *
|
Chris@0
|
662 * @return \DateTime A \DateTime instance
|
Chris@0
|
663 *
|
Chris@0
|
664 * @throws \RuntimeException When the header is not parseable
|
Chris@14
|
665 *
|
Chris@14
|
666 * @final since version 3.2
|
Chris@0
|
667 */
|
Chris@0
|
668 public function getDate()
|
Chris@0
|
669 {
|
Chris@0
|
670 return $this->headers->getDate('Date');
|
Chris@0
|
671 }
|
Chris@0
|
672
|
Chris@0
|
673 /**
|
Chris@0
|
674 * Sets the Date header.
|
Chris@0
|
675 *
|
Chris@14
|
676 * @return $this
|
Chris@0
|
677 *
|
Chris@14
|
678 * @final since version 3.2
|
Chris@0
|
679 */
|
Chris@0
|
680 public function setDate(\DateTime $date)
|
Chris@0
|
681 {
|
Chris@0
|
682 $date->setTimezone(new \DateTimeZone('UTC'));
|
Chris@0
|
683 $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
|
Chris@0
|
684
|
Chris@0
|
685 return $this;
|
Chris@0
|
686 }
|
Chris@0
|
687
|
Chris@0
|
688 /**
|
Chris@0
|
689 * Returns the age of the response.
|
Chris@0
|
690 *
|
Chris@0
|
691 * @return int The age of the response in seconds
|
Chris@14
|
692 *
|
Chris@14
|
693 * @final since version 3.2
|
Chris@0
|
694 */
|
Chris@0
|
695 public function getAge()
|
Chris@0
|
696 {
|
Chris@0
|
697 if (null !== $age = $this->headers->get('Age')) {
|
Chris@0
|
698 return (int) $age;
|
Chris@0
|
699 }
|
Chris@0
|
700
|
Chris@0
|
701 return max(time() - $this->getDate()->format('U'), 0);
|
Chris@0
|
702 }
|
Chris@0
|
703
|
Chris@0
|
704 /**
|
Chris@0
|
705 * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
|
Chris@0
|
706 *
|
Chris@0
|
707 * @return $this
|
Chris@0
|
708 */
|
Chris@0
|
709 public function expire()
|
Chris@0
|
710 {
|
Chris@0
|
711 if ($this->isFresh()) {
|
Chris@0
|
712 $this->headers->set('Age', $this->getMaxAge());
|
Chris@0
|
713 }
|
Chris@0
|
714
|
Chris@0
|
715 return $this;
|
Chris@0
|
716 }
|
Chris@0
|
717
|
Chris@0
|
718 /**
|
Chris@0
|
719 * Returns the value of the Expires header as a DateTime instance.
|
Chris@0
|
720 *
|
Chris@0
|
721 * @return \DateTime|null A DateTime instance or null if the header does not exist
|
Chris@14
|
722 *
|
Chris@14
|
723 * @final since version 3.2
|
Chris@0
|
724 */
|
Chris@0
|
725 public function getExpires()
|
Chris@0
|
726 {
|
Chris@0
|
727 try {
|
Chris@0
|
728 return $this->headers->getDate('Expires');
|
Chris@0
|
729 } catch (\RuntimeException $e) {
|
Chris@0
|
730 // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
|
Chris@0
|
731 return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
|
Chris@0
|
732 }
|
Chris@0
|
733 }
|
Chris@0
|
734
|
Chris@0
|
735 /**
|
Chris@0
|
736 * Sets the Expires HTTP header with a DateTime instance.
|
Chris@0
|
737 *
|
Chris@0
|
738 * Passing null as value will remove the header.
|
Chris@0
|
739 *
|
Chris@0
|
740 * @param \DateTime|null $date A \DateTime instance or null to remove the header
|
Chris@0
|
741 *
|
Chris@0
|
742 * @return $this
|
Chris@14
|
743 *
|
Chris@14
|
744 * @final since version 3.2
|
Chris@0
|
745 */
|
Chris@0
|
746 public function setExpires(\DateTime $date = null)
|
Chris@0
|
747 {
|
Chris@0
|
748 if (null === $date) {
|
Chris@0
|
749 $this->headers->remove('Expires');
|
Chris@0
|
750 } else {
|
Chris@0
|
751 $date = clone $date;
|
Chris@0
|
752 $date->setTimezone(new \DateTimeZone('UTC'));
|
Chris@0
|
753 $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
|
Chris@0
|
754 }
|
Chris@0
|
755
|
Chris@0
|
756 return $this;
|
Chris@0
|
757 }
|
Chris@0
|
758
|
Chris@0
|
759 /**
|
Chris@0
|
760 * Returns the number of seconds after the time specified in the response's Date
|
Chris@0
|
761 * header when the response should no longer be considered fresh.
|
Chris@0
|
762 *
|
Chris@0
|
763 * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
|
Chris@0
|
764 * back on an expires header. It returns null when no maximum age can be established.
|
Chris@0
|
765 *
|
Chris@0
|
766 * @return int|null Number of seconds
|
Chris@14
|
767 *
|
Chris@14
|
768 * @final since version 3.2
|
Chris@0
|
769 */
|
Chris@0
|
770 public function getMaxAge()
|
Chris@0
|
771 {
|
Chris@0
|
772 if ($this->headers->hasCacheControlDirective('s-maxage')) {
|
Chris@0
|
773 return (int) $this->headers->getCacheControlDirective('s-maxage');
|
Chris@0
|
774 }
|
Chris@0
|
775
|
Chris@0
|
776 if ($this->headers->hasCacheControlDirective('max-age')) {
|
Chris@0
|
777 return (int) $this->headers->getCacheControlDirective('max-age');
|
Chris@0
|
778 }
|
Chris@0
|
779
|
Chris@0
|
780 if (null !== $this->getExpires()) {
|
Chris@0
|
781 return $this->getExpires()->format('U') - $this->getDate()->format('U');
|
Chris@0
|
782 }
|
Chris@0
|
783 }
|
Chris@0
|
784
|
Chris@0
|
785 /**
|
Chris@0
|
786 * Sets the number of seconds after which the response should no longer be considered fresh.
|
Chris@0
|
787 *
|
Chris@0
|
788 * This methods sets the Cache-Control max-age directive.
|
Chris@0
|
789 *
|
Chris@0
|
790 * @param int $value Number of seconds
|
Chris@0
|
791 *
|
Chris@0
|
792 * @return $this
|
Chris@14
|
793 *
|
Chris@14
|
794 * @final since version 3.2
|
Chris@0
|
795 */
|
Chris@0
|
796 public function setMaxAge($value)
|
Chris@0
|
797 {
|
Chris@0
|
798 $this->headers->addCacheControlDirective('max-age', $value);
|
Chris@0
|
799
|
Chris@0
|
800 return $this;
|
Chris@0
|
801 }
|
Chris@0
|
802
|
Chris@0
|
803 /**
|
Chris@0
|
804 * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
|
Chris@0
|
805 *
|
Chris@0
|
806 * This methods sets the Cache-Control s-maxage directive.
|
Chris@0
|
807 *
|
Chris@0
|
808 * @param int $value Number of seconds
|
Chris@0
|
809 *
|
Chris@0
|
810 * @return $this
|
Chris@14
|
811 *
|
Chris@14
|
812 * @final since version 3.2
|
Chris@0
|
813 */
|
Chris@0
|
814 public function setSharedMaxAge($value)
|
Chris@0
|
815 {
|
Chris@0
|
816 $this->setPublic();
|
Chris@0
|
817 $this->headers->addCacheControlDirective('s-maxage', $value);
|
Chris@0
|
818
|
Chris@0
|
819 return $this;
|
Chris@0
|
820 }
|
Chris@0
|
821
|
Chris@0
|
822 /**
|
Chris@0
|
823 * Returns the response's time-to-live in seconds.
|
Chris@0
|
824 *
|
Chris@0
|
825 * It returns null when no freshness information is present in the response.
|
Chris@0
|
826 *
|
Chris@0
|
827 * When the responses TTL is <= 0, the response may not be served from cache without first
|
Chris@0
|
828 * revalidating with the origin.
|
Chris@0
|
829 *
|
Chris@0
|
830 * @return int|null The TTL in seconds
|
Chris@14
|
831 *
|
Chris@14
|
832 * @final since version 3.2
|
Chris@0
|
833 */
|
Chris@0
|
834 public function getTtl()
|
Chris@0
|
835 {
|
Chris@0
|
836 if (null !== $maxAge = $this->getMaxAge()) {
|
Chris@0
|
837 return $maxAge - $this->getAge();
|
Chris@0
|
838 }
|
Chris@0
|
839 }
|
Chris@0
|
840
|
Chris@0
|
841 /**
|
Chris@0
|
842 * Sets the response's time-to-live for shared caches.
|
Chris@0
|
843 *
|
Chris@0
|
844 * This method adjusts the Cache-Control/s-maxage directive.
|
Chris@0
|
845 *
|
Chris@0
|
846 * @param int $seconds Number of seconds
|
Chris@0
|
847 *
|
Chris@0
|
848 * @return $this
|
Chris@14
|
849 *
|
Chris@14
|
850 * @final since version 3.2
|
Chris@0
|
851 */
|
Chris@0
|
852 public function setTtl($seconds)
|
Chris@0
|
853 {
|
Chris@0
|
854 $this->setSharedMaxAge($this->getAge() + $seconds);
|
Chris@0
|
855
|
Chris@0
|
856 return $this;
|
Chris@0
|
857 }
|
Chris@0
|
858
|
Chris@0
|
859 /**
|
Chris@0
|
860 * Sets the response's time-to-live for private/client caches.
|
Chris@0
|
861 *
|
Chris@0
|
862 * This method adjusts the Cache-Control/max-age directive.
|
Chris@0
|
863 *
|
Chris@0
|
864 * @param int $seconds Number of seconds
|
Chris@0
|
865 *
|
Chris@0
|
866 * @return $this
|
Chris@14
|
867 *
|
Chris@14
|
868 * @final since version 3.2
|
Chris@0
|
869 */
|
Chris@0
|
870 public function setClientTtl($seconds)
|
Chris@0
|
871 {
|
Chris@0
|
872 $this->setMaxAge($this->getAge() + $seconds);
|
Chris@0
|
873
|
Chris@0
|
874 return $this;
|
Chris@0
|
875 }
|
Chris@0
|
876
|
Chris@0
|
877 /**
|
Chris@0
|
878 * Returns the Last-Modified HTTP header as a DateTime instance.
|
Chris@0
|
879 *
|
Chris@0
|
880 * @return \DateTime|null A DateTime instance or null if the header does not exist
|
Chris@0
|
881 *
|
Chris@0
|
882 * @throws \RuntimeException When the HTTP header is not parseable
|
Chris@14
|
883 *
|
Chris@14
|
884 * @final since version 3.2
|
Chris@0
|
885 */
|
Chris@0
|
886 public function getLastModified()
|
Chris@0
|
887 {
|
Chris@0
|
888 return $this->headers->getDate('Last-Modified');
|
Chris@0
|
889 }
|
Chris@0
|
890
|
Chris@0
|
891 /**
|
Chris@0
|
892 * Sets the Last-Modified HTTP header with a DateTime instance.
|
Chris@0
|
893 *
|
Chris@0
|
894 * Passing null as value will remove the header.
|
Chris@0
|
895 *
|
Chris@0
|
896 * @param \DateTime|null $date A \DateTime instance or null to remove the header
|
Chris@0
|
897 *
|
Chris@0
|
898 * @return $this
|
Chris@14
|
899 *
|
Chris@14
|
900 * @final since version 3.2
|
Chris@0
|
901 */
|
Chris@0
|
902 public function setLastModified(\DateTime $date = null)
|
Chris@0
|
903 {
|
Chris@0
|
904 if (null === $date) {
|
Chris@0
|
905 $this->headers->remove('Last-Modified');
|
Chris@0
|
906 } else {
|
Chris@0
|
907 $date = clone $date;
|
Chris@0
|
908 $date->setTimezone(new \DateTimeZone('UTC'));
|
Chris@0
|
909 $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
|
Chris@0
|
910 }
|
Chris@0
|
911
|
Chris@0
|
912 return $this;
|
Chris@0
|
913 }
|
Chris@0
|
914
|
Chris@0
|
915 /**
|
Chris@0
|
916 * Returns the literal value of the ETag HTTP header.
|
Chris@0
|
917 *
|
Chris@0
|
918 * @return string|null The ETag HTTP header or null if it does not exist
|
Chris@14
|
919 *
|
Chris@14
|
920 * @final since version 3.2
|
Chris@0
|
921 */
|
Chris@0
|
922 public function getEtag()
|
Chris@0
|
923 {
|
Chris@0
|
924 return $this->headers->get('ETag');
|
Chris@0
|
925 }
|
Chris@0
|
926
|
Chris@0
|
927 /**
|
Chris@0
|
928 * Sets the ETag value.
|
Chris@0
|
929 *
|
Chris@0
|
930 * @param string|null $etag The ETag unique identifier or null to remove the header
|
Chris@0
|
931 * @param bool $weak Whether you want a weak ETag or not
|
Chris@0
|
932 *
|
Chris@0
|
933 * @return $this
|
Chris@14
|
934 *
|
Chris@14
|
935 * @final since version 3.2
|
Chris@0
|
936 */
|
Chris@0
|
937 public function setEtag($etag = null, $weak = false)
|
Chris@0
|
938 {
|
Chris@0
|
939 if (null === $etag) {
|
Chris@0
|
940 $this->headers->remove('Etag');
|
Chris@0
|
941 } else {
|
Chris@0
|
942 if (0 !== strpos($etag, '"')) {
|
Chris@0
|
943 $etag = '"'.$etag.'"';
|
Chris@0
|
944 }
|
Chris@0
|
945
|
Chris@0
|
946 $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
|
Chris@0
|
947 }
|
Chris@0
|
948
|
Chris@0
|
949 return $this;
|
Chris@0
|
950 }
|
Chris@0
|
951
|
Chris@0
|
952 /**
|
Chris@0
|
953 * Sets the response's cache headers (validation and/or expiration).
|
Chris@0
|
954 *
|
Chris@14
|
955 * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
|
Chris@0
|
956 *
|
Chris@0
|
957 * @param array $options An array of cache options
|
Chris@0
|
958 *
|
Chris@0
|
959 * @return $this
|
Chris@0
|
960 *
|
Chris@0
|
961 * @throws \InvalidArgumentException
|
Chris@14
|
962 *
|
Chris@14
|
963 * @final since version 3.3
|
Chris@0
|
964 */
|
Chris@0
|
965 public function setCache(array $options)
|
Chris@0
|
966 {
|
Chris@14
|
967 if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'))) {
|
Chris@0
|
968 throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
|
Chris@0
|
969 }
|
Chris@0
|
970
|
Chris@0
|
971 if (isset($options['etag'])) {
|
Chris@0
|
972 $this->setEtag($options['etag']);
|
Chris@0
|
973 }
|
Chris@0
|
974
|
Chris@0
|
975 if (isset($options['last_modified'])) {
|
Chris@0
|
976 $this->setLastModified($options['last_modified']);
|
Chris@0
|
977 }
|
Chris@0
|
978
|
Chris@0
|
979 if (isset($options['max_age'])) {
|
Chris@0
|
980 $this->setMaxAge($options['max_age']);
|
Chris@0
|
981 }
|
Chris@0
|
982
|
Chris@0
|
983 if (isset($options['s_maxage'])) {
|
Chris@0
|
984 $this->setSharedMaxAge($options['s_maxage']);
|
Chris@0
|
985 }
|
Chris@0
|
986
|
Chris@0
|
987 if (isset($options['public'])) {
|
Chris@0
|
988 if ($options['public']) {
|
Chris@0
|
989 $this->setPublic();
|
Chris@0
|
990 } else {
|
Chris@0
|
991 $this->setPrivate();
|
Chris@0
|
992 }
|
Chris@0
|
993 }
|
Chris@0
|
994
|
Chris@0
|
995 if (isset($options['private'])) {
|
Chris@0
|
996 if ($options['private']) {
|
Chris@0
|
997 $this->setPrivate();
|
Chris@0
|
998 } else {
|
Chris@0
|
999 $this->setPublic();
|
Chris@0
|
1000 }
|
Chris@0
|
1001 }
|
Chris@0
|
1002
|
Chris@14
|
1003 if (isset($options['immutable'])) {
|
Chris@14
|
1004 $this->setImmutable((bool) $options['immutable']);
|
Chris@14
|
1005 }
|
Chris@14
|
1006
|
Chris@0
|
1007 return $this;
|
Chris@0
|
1008 }
|
Chris@0
|
1009
|
Chris@0
|
1010 /**
|
Chris@0
|
1011 * Modifies the response so that it conforms to the rules defined for a 304 status code.
|
Chris@0
|
1012 *
|
Chris@0
|
1013 * This sets the status, removes the body, and discards any headers
|
Chris@0
|
1014 * that MUST NOT be included in 304 responses.
|
Chris@0
|
1015 *
|
Chris@0
|
1016 * @return $this
|
Chris@0
|
1017 *
|
Chris@0
|
1018 * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
|
Chris@14
|
1019 *
|
Chris@14
|
1020 * @final since version 3.3
|
Chris@0
|
1021 */
|
Chris@0
|
1022 public function setNotModified()
|
Chris@0
|
1023 {
|
Chris@0
|
1024 $this->setStatusCode(304);
|
Chris@0
|
1025 $this->setContent(null);
|
Chris@0
|
1026
|
Chris@0
|
1027 // remove headers that MUST NOT be included with 304 Not Modified responses
|
Chris@0
|
1028 foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
|
Chris@0
|
1029 $this->headers->remove($header);
|
Chris@0
|
1030 }
|
Chris@0
|
1031
|
Chris@0
|
1032 return $this;
|
Chris@0
|
1033 }
|
Chris@0
|
1034
|
Chris@0
|
1035 /**
|
Chris@0
|
1036 * Returns true if the response includes a Vary header.
|
Chris@0
|
1037 *
|
Chris@0
|
1038 * @return bool true if the response includes a Vary header, false otherwise
|
Chris@14
|
1039 *
|
Chris@14
|
1040 * @final since version 3.2
|
Chris@0
|
1041 */
|
Chris@0
|
1042 public function hasVary()
|
Chris@0
|
1043 {
|
Chris@0
|
1044 return null !== $this->headers->get('Vary');
|
Chris@0
|
1045 }
|
Chris@0
|
1046
|
Chris@0
|
1047 /**
|
Chris@0
|
1048 * Returns an array of header names given in the Vary header.
|
Chris@0
|
1049 *
|
Chris@0
|
1050 * @return array An array of Vary names
|
Chris@14
|
1051 *
|
Chris@14
|
1052 * @final since version 3.2
|
Chris@0
|
1053 */
|
Chris@0
|
1054 public function getVary()
|
Chris@0
|
1055 {
|
Chris@0
|
1056 if (!$vary = $this->headers->get('Vary', null, false)) {
|
Chris@0
|
1057 return array();
|
Chris@0
|
1058 }
|
Chris@0
|
1059
|
Chris@0
|
1060 $ret = array();
|
Chris@0
|
1061 foreach ($vary as $item) {
|
Chris@0
|
1062 $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
|
Chris@0
|
1063 }
|
Chris@0
|
1064
|
Chris@0
|
1065 return $ret;
|
Chris@0
|
1066 }
|
Chris@0
|
1067
|
Chris@0
|
1068 /**
|
Chris@0
|
1069 * Sets the Vary header.
|
Chris@0
|
1070 *
|
Chris@0
|
1071 * @param string|array $headers
|
Chris@0
|
1072 * @param bool $replace Whether to replace the actual value or not (true by default)
|
Chris@0
|
1073 *
|
Chris@0
|
1074 * @return $this
|
Chris@14
|
1075 *
|
Chris@14
|
1076 * @final since version 3.2
|
Chris@0
|
1077 */
|
Chris@0
|
1078 public function setVary($headers, $replace = true)
|
Chris@0
|
1079 {
|
Chris@0
|
1080 $this->headers->set('Vary', $headers, $replace);
|
Chris@0
|
1081
|
Chris@0
|
1082 return $this;
|
Chris@0
|
1083 }
|
Chris@0
|
1084
|
Chris@0
|
1085 /**
|
Chris@0
|
1086 * Determines if the Response validators (ETag, Last-Modified) match
|
Chris@0
|
1087 * a conditional value specified in the Request.
|
Chris@0
|
1088 *
|
Chris@0
|
1089 * If the Response is not modified, it sets the status code to 304 and
|
Chris@0
|
1090 * removes the actual content by calling the setNotModified() method.
|
Chris@0
|
1091 *
|
Chris@14
|
1092 * @return bool true if the Response validators match the Request, false otherwise
|
Chris@0
|
1093 *
|
Chris@14
|
1094 * @final since version 3.3
|
Chris@0
|
1095 */
|
Chris@0
|
1096 public function isNotModified(Request $request)
|
Chris@0
|
1097 {
|
Chris@0
|
1098 if (!$request->isMethodCacheable()) {
|
Chris@0
|
1099 return false;
|
Chris@0
|
1100 }
|
Chris@0
|
1101
|
Chris@0
|
1102 $notModified = false;
|
Chris@0
|
1103 $lastModified = $this->headers->get('Last-Modified');
|
Chris@0
|
1104 $modifiedSince = $request->headers->get('If-Modified-Since');
|
Chris@0
|
1105
|
Chris@0
|
1106 if ($etags = $request->getETags()) {
|
Chris@0
|
1107 $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags);
|
Chris@0
|
1108 }
|
Chris@0
|
1109
|
Chris@0
|
1110 if ($modifiedSince && $lastModified) {
|
Chris@0
|
1111 $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
|
Chris@0
|
1112 }
|
Chris@0
|
1113
|
Chris@0
|
1114 if ($notModified) {
|
Chris@0
|
1115 $this->setNotModified();
|
Chris@0
|
1116 }
|
Chris@0
|
1117
|
Chris@0
|
1118 return $notModified;
|
Chris@0
|
1119 }
|
Chris@0
|
1120
|
Chris@0
|
1121 /**
|
Chris@0
|
1122 * Is response invalid?
|
Chris@0
|
1123 *
|
Chris@0
|
1124 * @return bool
|
Chris@0
|
1125 *
|
Chris@0
|
1126 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
Chris@14
|
1127 *
|
Chris@14
|
1128 * @final since version 3.2
|
Chris@0
|
1129 */
|
Chris@0
|
1130 public function isInvalid()
|
Chris@0
|
1131 {
|
Chris@0
|
1132 return $this->statusCode < 100 || $this->statusCode >= 600;
|
Chris@0
|
1133 }
|
Chris@0
|
1134
|
Chris@0
|
1135 /**
|
Chris@0
|
1136 * Is response informative?
|
Chris@0
|
1137 *
|
Chris@0
|
1138 * @return bool
|
Chris@14
|
1139 *
|
Chris@14
|
1140 * @final since version 3.3
|
Chris@0
|
1141 */
|
Chris@0
|
1142 public function isInformational()
|
Chris@0
|
1143 {
|
Chris@0
|
1144 return $this->statusCode >= 100 && $this->statusCode < 200;
|
Chris@0
|
1145 }
|
Chris@0
|
1146
|
Chris@0
|
1147 /**
|
Chris@0
|
1148 * Is response successful?
|
Chris@0
|
1149 *
|
Chris@0
|
1150 * @return bool
|
Chris@14
|
1151 *
|
Chris@14
|
1152 * @final since version 3.2
|
Chris@0
|
1153 */
|
Chris@0
|
1154 public function isSuccessful()
|
Chris@0
|
1155 {
|
Chris@0
|
1156 return $this->statusCode >= 200 && $this->statusCode < 300;
|
Chris@0
|
1157 }
|
Chris@0
|
1158
|
Chris@0
|
1159 /**
|
Chris@0
|
1160 * Is the response a redirect?
|
Chris@0
|
1161 *
|
Chris@0
|
1162 * @return bool
|
Chris@14
|
1163 *
|
Chris@14
|
1164 * @final since version 3.2
|
Chris@0
|
1165 */
|
Chris@0
|
1166 public function isRedirection()
|
Chris@0
|
1167 {
|
Chris@0
|
1168 return $this->statusCode >= 300 && $this->statusCode < 400;
|
Chris@0
|
1169 }
|
Chris@0
|
1170
|
Chris@0
|
1171 /**
|
Chris@0
|
1172 * Is there a client error?
|
Chris@0
|
1173 *
|
Chris@0
|
1174 * @return bool
|
Chris@14
|
1175 *
|
Chris@14
|
1176 * @final since version 3.2
|
Chris@0
|
1177 */
|
Chris@0
|
1178 public function isClientError()
|
Chris@0
|
1179 {
|
Chris@0
|
1180 return $this->statusCode >= 400 && $this->statusCode < 500;
|
Chris@0
|
1181 }
|
Chris@0
|
1182
|
Chris@0
|
1183 /**
|
Chris@0
|
1184 * Was there a server side error?
|
Chris@0
|
1185 *
|
Chris@0
|
1186 * @return bool
|
Chris@14
|
1187 *
|
Chris@14
|
1188 * @final since version 3.3
|
Chris@0
|
1189 */
|
Chris@0
|
1190 public function isServerError()
|
Chris@0
|
1191 {
|
Chris@0
|
1192 return $this->statusCode >= 500 && $this->statusCode < 600;
|
Chris@0
|
1193 }
|
Chris@0
|
1194
|
Chris@0
|
1195 /**
|
Chris@0
|
1196 * Is the response OK?
|
Chris@0
|
1197 *
|
Chris@0
|
1198 * @return bool
|
Chris@14
|
1199 *
|
Chris@14
|
1200 * @final since version 3.2
|
Chris@0
|
1201 */
|
Chris@0
|
1202 public function isOk()
|
Chris@0
|
1203 {
|
Chris@0
|
1204 return 200 === $this->statusCode;
|
Chris@0
|
1205 }
|
Chris@0
|
1206
|
Chris@0
|
1207 /**
|
Chris@0
|
1208 * Is the response forbidden?
|
Chris@0
|
1209 *
|
Chris@0
|
1210 * @return bool
|
Chris@14
|
1211 *
|
Chris@14
|
1212 * @final since version 3.2
|
Chris@0
|
1213 */
|
Chris@0
|
1214 public function isForbidden()
|
Chris@0
|
1215 {
|
Chris@0
|
1216 return 403 === $this->statusCode;
|
Chris@0
|
1217 }
|
Chris@0
|
1218
|
Chris@0
|
1219 /**
|
Chris@0
|
1220 * Is the response a not found error?
|
Chris@0
|
1221 *
|
Chris@0
|
1222 * @return bool
|
Chris@14
|
1223 *
|
Chris@14
|
1224 * @final since version 3.2
|
Chris@0
|
1225 */
|
Chris@0
|
1226 public function isNotFound()
|
Chris@0
|
1227 {
|
Chris@0
|
1228 return 404 === $this->statusCode;
|
Chris@0
|
1229 }
|
Chris@0
|
1230
|
Chris@0
|
1231 /**
|
Chris@0
|
1232 * Is the response a redirect of some form?
|
Chris@0
|
1233 *
|
Chris@0
|
1234 * @param string $location
|
Chris@0
|
1235 *
|
Chris@0
|
1236 * @return bool
|
Chris@14
|
1237 *
|
Chris@14
|
1238 * @final since version 3.2
|
Chris@0
|
1239 */
|
Chris@0
|
1240 public function isRedirect($location = null)
|
Chris@0
|
1241 {
|
Chris@0
|
1242 return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
|
Chris@0
|
1243 }
|
Chris@0
|
1244
|
Chris@0
|
1245 /**
|
Chris@0
|
1246 * Is the response empty?
|
Chris@0
|
1247 *
|
Chris@0
|
1248 * @return bool
|
Chris@14
|
1249 *
|
Chris@14
|
1250 * @final since version 3.2
|
Chris@0
|
1251 */
|
Chris@0
|
1252 public function isEmpty()
|
Chris@0
|
1253 {
|
Chris@0
|
1254 return in_array($this->statusCode, array(204, 304));
|
Chris@0
|
1255 }
|
Chris@0
|
1256
|
Chris@0
|
1257 /**
|
Chris@0
|
1258 * Cleans or flushes output buffers up to target level.
|
Chris@0
|
1259 *
|
Chris@0
|
1260 * Resulting level can be greater than target level if a non-removable buffer has been encountered.
|
Chris@0
|
1261 *
|
Chris@0
|
1262 * @param int $targetLevel The target output buffering level
|
Chris@0
|
1263 * @param bool $flush Whether to flush or clean the buffers
|
Chris@14
|
1264 *
|
Chris@14
|
1265 * @final since version 3.3
|
Chris@0
|
1266 */
|
Chris@0
|
1267 public static function closeOutputBuffers($targetLevel, $flush)
|
Chris@0
|
1268 {
|
Chris@0
|
1269 $status = ob_get_status(true);
|
Chris@0
|
1270 $level = count($status);
|
Chris@0
|
1271 // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
|
Chris@0
|
1272 $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
|
Chris@0
|
1273
|
Chris@14
|
1274 while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
|
Chris@0
|
1275 if ($flush) {
|
Chris@0
|
1276 ob_end_flush();
|
Chris@0
|
1277 } else {
|
Chris@0
|
1278 ob_end_clean();
|
Chris@0
|
1279 }
|
Chris@0
|
1280 }
|
Chris@0
|
1281 }
|
Chris@0
|
1282
|
Chris@0
|
1283 /**
|
Chris@0
|
1284 * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
|
Chris@0
|
1285 *
|
Chris@0
|
1286 * @see http://support.microsoft.com/kb/323308
|
Chris@14
|
1287 *
|
Chris@14
|
1288 * @final since version 3.3
|
Chris@0
|
1289 */
|
Chris@0
|
1290 protected function ensureIEOverSSLCompatibility(Request $request)
|
Chris@0
|
1291 {
|
Chris@14
|
1292 if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
|
Chris@0
|
1293 if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
|
Chris@0
|
1294 $this->headers->remove('Cache-Control');
|
Chris@0
|
1295 }
|
Chris@0
|
1296 }
|
Chris@0
|
1297 }
|
Chris@0
|
1298 }
|