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