const HTTP_CONTINUE = 100;
const HTTP_SWITCHING_PROTOCOLS = 101;
const HTTP_PROCESSING = 102; // RFC2518
+ const HTTP_EARLY_HINTS = 103; // RFC8297
const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_ACCEPTED = 202;
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', // RFC2518
+ 103 => 'Early Hints',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
511 => 'Network Authentication Required', // RFC6585
);
- private static $deprecatedMethods = array(
- 'setDate', 'getDate',
- 'setExpires', 'getExpires',
- 'setLastModified', 'getLastModified',
- 'setProtocolVersion', 'getProtocolVersion',
- 'setStatusCode', 'getStatusCode',
- 'setCharset', 'getCharset',
- 'setPrivate', 'setPublic',
- 'getAge', 'getMaxAge', 'setMaxAge', 'setSharedMaxAge',
- 'getTtl', 'setTtl', 'setClientTtl',
- 'getEtag', 'setEtag',
- 'hasVary', 'getVary', 'setVary',
- 'isInvalid', 'isSuccessful', 'isRedirection',
- 'isClientError', 'isOk', 'isForbidden',
- 'isNotFound', 'isRedirect', 'isEmpty',
- );
- private static $deprecationsTriggered = array(
- __CLASS__ => true,
- BinaryFileResponse::class => true,
- JsonResponse::class => true,
- RedirectResponse::class => true,
- StreamedResponse::class => true,
- );
-
/**
- * Constructor.
- *
* @param mixed $content The response content, see setContent()
* @param int $status The response status code
* @param array $headers An array of response headers
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
-
- /* RFC2616 - 14.18 says all Responses need to have a Date */
- if (!$this->headers->has('Date')) {
- $this->setDate(\DateTime::createFromFormat('U', time()));
- }
-
- // Deprecations
- $class = get_class($this);
- if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) {
- $class = get_parent_class($class);
- }
- if (isset(self::$deprecationsTriggered[$class])) {
- return;
- }
-
- self::$deprecationsTriggered[$class] = true;
- foreach (self::$deprecatedMethods as $method) {
- $r = new \ReflectionMethod($class, $method);
- if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
- @trigger_error(sprintf('Extending %s::%s() in %s is deprecated since version 3.2 and won\'t be supported anymore in 4.0 as it will be final.', __CLASS__, $method, $class), E_USER_DEPRECATED);
- }
- }
}
/**
* compliant with RFC 2616. Most of the changes are based on
* the Request that is "associated" with this Response.
*
- * @param Request $request A Request instance
- *
* @return $this
*/
public function prepare(Request $request)
return $this;
}
- /* RFC2616 - 14.18 says all Responses need to have a Date */
- if (!$this->headers->has('Date')) {
- $this->setDate(\DateTime::createFromFormat('U', time()));
- }
-
// headers
foreach ($this->headers->allPreserveCase() as $name => $values) {
foreach ($values as $value) {
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
- // cookies
- foreach ($this->headers->getCookies() as $cookie) {
- if ($cookie->isRaw()) {
- setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
- } else {
- setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
- }
- }
-
return $this;
}
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
- } elseif ('cli' !== PHP_SAPI) {
+ } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}
* @param string $version The HTTP protocol version
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setProtocolVersion($version)
{
* Gets the HTTP protocol version.
*
* @return string The HTTP protocol version
+ *
+ * @final since version 3.2
*/
public function getProtocolVersion()
{
/**
* Sets the response status code.
*
- * @param int $code HTTP status code
- * @param mixed $text HTTP status text
- *
* If the status text is null it will be automatically populated for the known
* status codes and left empty otherwise.
*
+ * @param int $code HTTP status code
+ * @param mixed $text HTTP status text
+ *
* @return $this
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
+ *
+ * @final since version 3.2
*/
public function setStatusCode($code, $text = null)
{
* Retrieves the status code for the current web response.
*
* @return int Status code
+ *
+ * @final since version 3.2
*/
public function getStatusCode()
{
* @param string $charset Character set
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setCharset($charset)
{
* Retrieves the response charset.
*
* @return string Character set
+ *
+ * @final since version 3.2
*/
public function getCharset()
{
}
/**
- * Returns true if the response is worth caching under any circumstance.
+ * Returns true if the response may safely be kept in a shared (surrogate) cache.
*
* Responses marked "private" with an explicit Cache-Control directive are
* considered uncacheable.
*
* Responses with neither a freshness lifetime (Expires, max-age) nor cache
- * validator (Last-Modified, ETag) are considered uncacheable.
+ * validator (Last-Modified, ETag) are considered uncacheable because there is
+ * no way to tell when or how to remove them from the cache.
+ *
+ * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
+ * for example "status codes that are defined as cacheable by default [...]
+ * can be reused by a cache with heuristic expiration unless otherwise indicated"
+ * (https://tools.ietf.org/html/rfc7231#section-6.1)
*
* @return bool true if the response is worth caching, false otherwise
+ *
+ * @final since version 3.3
*/
public function isCacheable()
{
* indicator or Expires header and the calculated age is less than the freshness lifetime.
*
* @return bool true if the response is fresh, false otherwise
+ *
+ * @final since version 3.3
*/
public function isFresh()
{
* the response with the origin server using a conditional GET request.
*
* @return bool true if the response is validateable, false otherwise
+ *
+ * @final since version 3.3
*/
public function isValidateable()
{
* It makes the response ineligible for serving other clients.
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setPrivate()
{
* It makes the response eligible for serving other clients.
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setPublic()
{
return $this;
}
+ /**
+ * Marks the response as "immutable".
+ *
+ * @param bool $immutable enables or disables the immutable directive
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setImmutable($immutable = true)
+ {
+ if ($immutable) {
+ $this->headers->addCacheControlDirective('immutable');
+ } else {
+ $this->headers->removeCacheControlDirective('immutable');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response is marked as "immutable".
+ *
+ * @return bool returns true if the response is marked as "immutable"; otherwise false
+ *
+ * @final
+ */
+ public function isImmutable()
+ {
+ return $this->headers->hasCacheControlDirective('immutable');
+ }
+
/**
* Returns true if the response must be revalidated by caches.
*
* greater than the value provided by the origin.
*
* @return bool true if the response must be revalidated by a cache, false otherwise
+ *
+ * @final since version 3.3
*/
public function mustRevalidate()
{
* @return \DateTime A \DateTime instance
*
* @throws \RuntimeException When the header is not parseable
+ *
+ * @final since version 3.2
*/
public function getDate()
{
- /*
- RFC2616 - 14.18 says all Responses need to have a Date.
- Make sure we provide one even if it the header
- has been removed in the meantime.
- */
- if (!$this->headers->has('Date')) {
- $this->setDate(\DateTime::createFromFormat('U', time()));
- }
-
return $this->headers->getDate('Date');
}
/**
* Sets the Date header.
*
- * @param \DateTime $date A \DateTime instance
- *
* @return $this
+ *
+ * @final since version 3.2
*/
public function setDate(\DateTime $date)
{
* Returns the age of the response.
*
* @return int The age of the response in seconds
+ *
+ * @final since version 3.2
*/
public function getAge()
{
* Returns the value of the Expires header as a DateTime instance.
*
* @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @final since version 3.2
*/
public function getExpires()
{
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setExpires(\DateTime $date = null)
{
* back on an expires header. It returns null when no maximum age can be established.
*
* @return int|null Number of seconds
+ *
+ * @final since version 3.2
*/
public function getMaxAge()
{
* @param int $value Number of seconds
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setMaxAge($value)
{
* @param int $value Number of seconds
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setSharedMaxAge($value)
{
* revalidating with the origin.
*
* @return int|null The TTL in seconds
+ *
+ * @final since version 3.2
*/
public function getTtl()
{
* @param int $seconds Number of seconds
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setTtl($seconds)
{
* @param int $seconds Number of seconds
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setClientTtl($seconds)
{
* @return \DateTime|null A DateTime instance or null if the header does not exist
*
* @throws \RuntimeException When the HTTP header is not parseable
+ *
+ * @final since version 3.2
*/
public function getLastModified()
{
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setLastModified(\DateTime $date = null)
{
* Returns the literal value of the ETag HTTP header.
*
* @return string|null The ETag HTTP header or null if it does not exist
+ *
+ * @final since version 3.2
*/
public function getEtag()
{
* @param bool $weak Whether you want a weak ETag or not
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setEtag($etag = null, $weak = false)
{
/**
* Sets the response's cache headers (validation and/or expiration).
*
- * Available options are: etag, last_modified, max_age, s_maxage, private, and public.
+ * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
*
* @param array $options An array of cache options
*
* @return $this
*
* @throws \InvalidArgumentException
+ *
+ * @final since version 3.3
*/
public function setCache(array $options)
{
- if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) {
+ if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'))) {
throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
}
}
}
+ if (isset($options['immutable'])) {
+ $this->setImmutable((bool) $options['immutable']);
+ }
+
return $this;
}
* @return $this
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3.5
+ *
+ * @final since version 3.3
*/
public function setNotModified()
{
* Returns true if the response includes a Vary header.
*
* @return bool true if the response includes a Vary header, false otherwise
+ *
+ * @final since version 3.2
*/
public function hasVary()
{
* Returns an array of header names given in the Vary header.
*
* @return array An array of Vary names
+ *
+ * @final since version 3.2
*/
public function getVary()
{
* @param bool $replace Whether to replace the actual value or not (true by default)
*
* @return $this
+ *
+ * @final since version 3.2
*/
public function setVary($headers, $replace = true)
{
* If the Response is not modified, it sets the status code to 304 and
* removes the actual content by calling the setNotModified() method.
*
- * @param Request $request A Request instance
- *
* @return bool true if the Response validators match the Request, false otherwise
+ *
+ * @final since version 3.3
*/
public function isNotModified(Request $request)
{
* @return bool
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ *
+ * @final since version 3.2
*/
public function isInvalid()
{
* Is response informative?
*
* @return bool
+ *
+ * @final since version 3.3
*/
public function isInformational()
{
* Is response successful?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isSuccessful()
{
* Is the response a redirect?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isRedirection()
{
* Is there a client error?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isClientError()
{
* Was there a server side error?
*
* @return bool
+ *
+ * @final since version 3.3
*/
public function isServerError()
{
* Is the response OK?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isOk()
{
* Is the response forbidden?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isForbidden()
{
* Is the response a not found error?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isNotFound()
{
* @param string $location
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isRedirect($location = null)
{
* Is the response empty?
*
* @return bool
+ *
+ * @final since version 3.2
*/
public function isEmpty()
{
*
* @param int $targetLevel The target output buffering level
* @param bool $flush Whether to flush or clean the buffers
+ *
+ * @final since version 3.3
*/
public static function closeOutputBuffers($targetLevel, $flush)
{
// PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
$flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
- while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) {
+ while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
if ($flush) {
ob_end_flush();
} else {
* Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
*
* @see http://support.microsoft.com/kb/323308
+ *
+ * @final since version 3.3
*/
protected function ensureIEOverSSLCompatibility(Request $request)
{
- if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) {
+ if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
$this->headers->remove('Cache-Control');
}