changed Cache-Control default value behavior

The PHP native cache limiter feature has been disabled as this is now managed
by the HeaderBag class directly instead (see below.)

The HeaderBag class uses the following rules to define a sensible and
convervative default value for the Response 'Cache-Control' header:

 * If no cache header is defined ('Cache-Control', 'ETag', 'Last-Modified',
   and 'Expires'), 'Cache-Control' is set to 'no-cache';

 * If 'Cache-Control' is empty, its value is set to "private, max-age=0,
   must-revalidate";

 * But if at least one 'Cache-Control' directive is set, and no 'public' or
   'private' directives have been explicitely added, Symfony2 adds the
   'private' directive automatically (except when 's-maxage' is set.)

So, remember to explicitly add the 'public' directive to 'Cache-Control' when
you want shared caches to store your application resources:

    // The Response is private by default
    $response->setEtag($etag);
    $response->setLastModified($date);
    $response->setMaxAge(10);

    // Change the Response to be public
    $response->setPublic();

    // Set cache settings in one call
    $response->setCache(array(
        'etag'          => $etag,
        'last_modified' => $date,
        'max_age'       => 10,
        'public'        => true,
    ));
This commit is contained in:
Fabien Potencier 2010-11-10 10:48:22 +01:00
parent d9239d1c64
commit b6923dd7b9
14 changed files with 373 additions and 438 deletions

View File

@ -1,288 +0,0 @@
<?php
namespace Symfony\Component\HttpFoundation;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* CacheControl is a wrapper for the Cache-Control HTTP header.
*
* This class knows about allowed attributes
* (and those that only apply to requests or responses).
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class CacheControl
{
protected $bag;
protected $attributes;
protected $type;
/**
* Constructor.
*
* @param HeaderBag $bag A HeaderBag instance
* @param string $header The value of the Cache-Control HTTP header
* @param string $type The type (null, request, or response)
*/
public function __construct(HeaderBag $bag, $header, $type = null)
{
if (null !== $type && !in_array($type, array('request', 'response'))) {
throw new \InvalidArgumentException(sprintf('The "%s" type is not supported by the CacheControl constructor.', $type));
}
$this->type = $type;
$this->bag = $bag;
$this->attributes = $this->parse($header);
}
public function __toString()
{
$parts = array();
ksort($this->attributes);
foreach ($this->attributes as $key => $value) {
if (true === $value) {
$parts[] = $key;
} else {
if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
$value = '"'.$value.'"';
}
$parts[] = "$key=$value";
}
}
return implode(',', $parts);
}
public function getMaxStale()
{
$this->checkAttribute('max-stale', 'request');
return array_key_exists('max-stale', $this->attributes) ? $this->attributes['max-stale'] : false;
}
public function setMaxStale($value)
{
$this->checkAttribute('max-stale', 'request');
$this->setValue('max-stale', $value);
}
public function getMinFresh()
{
$this->checkAttribute('min-fresh', 'request');
return array_key_exists('min-fresh', $this->attributes) ? $this->attributes['min-fresh'] : false;
}
public function setMinFresh($value)
{
$this->checkAttribute('min-fresh', 'request');
$this->setValue('min-fresh', $value);
}
public function isOnlyIfCached()
{
$this->checkAttribute('only-if-cached', 'request');
return array_key_exists('only-if-cached', $this->attributes);
}
public function setOnlyIfCached($value)
{
$this->checkAttribute('only-if-cached', 'request');
$this->setValue('only-if-cached', $value, true);
}
public function isPublic()
{
$this->checkAttribute('public', 'response');
return array_key_exists('public', $this->attributes);
}
public function setPublic($value)
{
$this->checkAttribute('public', 'response');
$this->setValue('public', $value, true);
}
public function isPrivate()
{
$this->checkAttribute('private', 'response');
return array_key_exists('private', $this->attributes);
}
public function getPrivate()
{
$this->checkAttribute('private', 'response');
return array_key_exists('private', $this->attributes) ? $this->attributes['private'] : false;
}
public function setPrivate($value)
{
$this->checkAttribute('private', 'response');
$this->setValue('private', $value, true);
}
public function isNoCache()
{
return array_key_exists('no-cache', $this->attributes);
}
public function getNoCache()
{
return array_key_exists('no-cache', $this->attributes) ? $this->attributes['no-cache'] : false;
}
public function setNoCache($value)
{
$this->setValue('no-cache', $value, true);
}
public function isNoStore()
{
return array_key_exists('no-store', $this->attributes);
}
public function setNoStore($value)
{
$this->setValue('no-store', $value, true);
}
public function isNoTransform()
{
return array_key_exists('no-tranform', $this->attributes);
}
public function setNoTransform($value)
{
$this->setValue('no-transform', $value, true);
}
public function getMaxAge()
{
return array_key_exists('max-age', $this->attributes) ? $this->attributes['max-age'] : null;
}
public function setMaxAge($age)
{
$this->setValue('max-age', (integer) $age);
}
public function getSharedMaxAge()
{
$this->checkAttribute('s-maxage', 'response');
return array_key_exists('s-maxage', $this->attributes) ? $this->attributes['s-maxage'] : null;
}
public function setSharedMaxAge($age)
{
$this->checkAttribute('s-maxage', 'response');
$this->setValue('s-maxage', (integer) $age);
}
public function setStaleWhileRevalidate($age)
{
$this->checkAttribute('stale-while-revalidate', 'response');
$this->setValue('stale-while-revalidate', (integer) $age);
}
public function getStaleWhileRevalidate()
{
$this->checkAttribute('stale-while-revalidate', 'response');
return array_key_exists('stale-while-revalidate', $this->attributes) ? $this->attributes['stale-while-revalidate'] : null;
}
public function setStaleIfError($age)
{
$this->setValue('stale-if-error', (integer) $age);
}
public function getStaleIfError()
{
return array_key_exists('stale-if-error', $this->attributes) ? $this->attributes['stale-if-error'] : null;
}
public function mustRevalidate()
{
$this->checkAttribute('must-revalidate', 'response');
return array_key_exists('must-revalidate', $this->attributes);
}
public function setMustRevalidate($value)
{
$this->checkAttribute('must-revalidate', 'response');
$this->setValue('must-revalidate', $value);
}
public function mustProxyRevalidate()
{
$this->checkAttribute('proxy-revalidate', 'response');
return array_key_exists('proxy-revalidate', $this->attributes);
}
public function setProxyRevalidate($value)
{
$this->checkAttribute('proxy-revalidate', 'response');
$this->setValue('proxy-revalidate', $value);
}
/**
* Parses a Cache-Control HTTP header.
*
* @param string $header The value of the Cache-Control HTTP header
*
* @return array An array representing the attribute values
*/
protected function parse($header)
{
$attributes = array();
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$attributes[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true);
}
return $attributes;
}
protected function setValue($key, $value, $isBoolean = false)
{
if (false === $value) {
unset($this->attributes[$key]);
} else {
$this->attributes[$key] = $isBoolean ? true : $value;
}
$this->bag->set('Cache-Control', (string) $this);
}
protected function checkAttribute($name, $expected)
{
if (null !== $this->type && $expected !== $this->type) {
throw new \LogicException(sprintf("The property %s only applies to the %s Cache-Control.", $name, $expected));
}
}
}

View File

@ -20,22 +20,16 @@ class HeaderBag
{
protected $headers;
protected $cacheControl;
protected $type;
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
* @param string $type The type (null, request, or response)
* @param array $headers An array of HTTP headers
*/
public function __construct(array $headers = array(), $type = null)
public function __construct(array $headers = array())
{
$this->cacheControl = array();
$this->replace($headers);
if (null !== $type && !in_array($type, array('request', 'response'))) {
throw new \InvalidArgumentException(sprintf('The "%s" type is not supported by the HeaderBag constructor.', $type));
}
$this->type = $type;
}
/**
@ -65,7 +59,6 @@ class HeaderBag
*/
public function replace(array $headers = array())
{
$this->cacheControl = null;
$this->headers = array();
foreach ($headers as $key => $values) {
$this->set($key, $values);
@ -120,6 +113,10 @@ class HeaderBag
} else {
$this->headers[$key] = array_merge($this->headers[$key], $values);
}
if ('cache-control' === $key) {
$this->cacheControl = $this->parseCacheControl($values[0]);
}
}
/**
@ -154,21 +151,13 @@ class HeaderBag
*/
public function delete($key)
{
unset($this->headers[strtr(strtolower($key), '_', '-')]);
}
$key = strtr(strtolower($key), '_', '-');
/**
* Returns an instance able to manage the Cache-Control header.
*
* @return CacheControl A CacheControl instance
*/
public function getCacheControl()
{
if (null === $this->cacheControl) {
$this->cacheControl = new CacheControl($this, $this->get('Cache-Control'), $this->type);
unset($this->headers[$key]);
if ('cache-control' === $key) {
$this->cacheControl = array();
}
return $this->cacheControl;
}
/**
@ -186,55 +175,9 @@ class HeaderBag
*/
public function setCookie($name, $value, $domain = null, $expires = null, $path = '/', $secure = false, $httponly = true)
{
// from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
$this->validateCookieName($name, $value);
if (preg_match("/[,; \t\r\n\013\014]/", $value)) {
throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $name));
}
if (!$name) {
throw new \InvalidArgumentException('The cookie name cannot be empty');
}
$cookie = sprintf('%s=%s', $name, urlencode($value));
if ('request' === $this->type) {
return $this->set('Cookie', $cookie);
}
if (null !== $expires) {
if (is_numeric($expires)) {
$expires = (int) $expires;
} elseif ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} else {
$expires = strtotime($expires);
if (false === $expires || -1 == $expires) {
throw new \InvalidArgumentException(sprintf('The "expires" cookie parameter is not valid.', $expires));
}
}
$cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), 0, -5);
}
if ($domain) {
$cookie .= '; domain='.$domain;
}
$cookie .= '; path='.$path;
if ($secure) {
$cookie .= '; secure';
}
if ($httponly) {
$cookie .= '; httponly';
}
$this->set('Set-Cookie', $cookie, false);
return $this->set('Cookie', sprintf('%s=%s', $name, urlencode($value)));
}
/**
@ -258,15 +201,80 @@ class HeaderBag
return $date;
}
/**
* Normalizes a HTTP header name.
*
* @param string $key The HTTP header name
*
* @return string The normalized HTTP header name
*/
static public function normalizeHeaderName($key)
public function addCacheControlDirective($key, $value = true)
{
return strtr(strtolower($key), '_', '-');
$this->cacheControl[$key] = $value;
$this->set('Cache-Control', $this->getCacheControlHeader());
}
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl);
}
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
}
public function removeCacheControlDirective($key)
{
unset($this->cacheControl[$key]);
$this->set('Cache-Control', $this->getCacheControlHeader());
}
protected function getCacheControlHeader()
{
$parts = array();
ksort($this->cacheControl);
foreach ($this->cacheControl as $key => $value) {
if (true === $value) {
$parts[] = $key;
} else {
if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
$value = '"'.$value.'"';
}
$parts[] = "$key=$value";
}
}
return implode(', ', $parts);
}
/**
* Parses a Cache-Control HTTP header.
*
* @param string $header The value of the Cache-Control HTTP header
*
* @return array An array representing the attribute values
*/
protected function parseCacheControl($header)
{
$cacheControl = array();
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$cacheControl[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true);
}
return $cacheControl;
}
protected function validateCookie($name, $value)
{
// from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
if (preg_match("/[,; \t\r\n\013\014]/", $value)) {
throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $name));
}
if (!$name) {
throw new \InvalidArgumentException('The cookie name cannot be empty');
}
}
}

View File

@ -104,7 +104,7 @@ class Request
$this->cookies = new ParameterBag(null !== $cookies ? $cookies : $_COOKIE);
$this->files = new ParameterBag($this->convertFileInformation(null !== $files ? $files : $_FILES));
$this->server = new ParameterBag(null !== $server ? $server : $_SERVER);
$this->headers = new HeaderBag($this->initializeHeaders(), 'request');
$this->headers = new HeaderBag($this->initializeHeaders());
$this->languages = null;
$this->charsets = null;
@ -617,7 +617,7 @@ class Request
public function isNoCache()
{
return $this->headers->getCacheControl()->isNoCache() || 'no-cache' == $this->headers->get('Pragma');
return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
}
/**

View File

@ -83,7 +83,7 @@ class Response
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
$this->headers = new HeaderBag($headers, 'response');
$this->headers = new ResponseHeaderBag($headers);
}
/**
@ -244,7 +244,7 @@ class Response
return false;
}
if ($this->headers->getCacheControl()->isNoStore() || $this->headers->getCacheControl()->isPrivate()) {
if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
return false;
}
@ -283,8 +283,8 @@ class Response
*/
public function setPrivate()
{
$this->headers->getCacheControl()->setPublic(false);
$this->headers->getCacheControl()->setPrivate(true);
$this->headers->removeCacheControlDirective('public');
$this->headers->addCacheControlDirective('private');
}
/**
@ -294,8 +294,8 @@ class Response
*/
public function setPublic()
{
$this->headers->getCacheControl()->setPublic(true);
$this->headers->getCacheControl()->setPrivate(false);
$this->headers->addCacheControlDirective('public');
$this->headers->removeCacheControlDirective('private');
}
/**
@ -310,7 +310,7 @@ class Response
*/
public function mustRevalidate()
{
return $this->headers->getCacheControl()->mustRevalidate() || $this->headers->getCacheControl()->mustProxyRevalidate();
return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->has('must-proxy-revalidate');
}
/**
@ -395,11 +395,11 @@ class Response
*/
public function getMaxAge()
{
if ($age = $this->headers->getCacheControl()->getSharedMaxAge()) {
if ($age = $this->headers->getCacheControlDirective('s-maxage')) {
return $age;
}
if ($age = $this->headers->getCacheControl()->getMaxAge()) {
if ($age = $this->headers->getCacheControlDirective('max-age')) {
return $age;
}
@ -419,7 +419,7 @@ class Response
*/
public function setMaxAge($value)
{
$this->headers->getCacheControl()->setMaxAge($value);
$this->headers->addCacheControlDirective('max-age', $value);
}
/**
@ -431,7 +431,7 @@ class Response
*/
public function setSharedMaxAge($value)
{
$this->headers->getCacheControl()->setSharedMaxAge($value);
$this->headers->addCacheControlDirective('s-maxage', $value);
}
/**

View File

@ -0,0 +1,141 @@
<?php
namespace Symfony\Component\HttpFoundation;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* ResponseHeaderBag is a container for Response HTTP headers.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ResponseHeaderBag extends HeaderBag
{
protected $computedCacheControl = array();
/**
* {@inheritdoc}
*/
public function replace(array $headers = array())
{
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('cache-control', '');
}
}
/**
* {@inheritdoc}
*/
public function set($key, $values, $replace = true)
{
parent::set($key, $values, $replace);
if ('cache-control' === strtr(strtolower($key), '_', '-')) {
$computed = $this->computeCacheControlValue();
$this->headers['cache-control'] = array($computed);
$this->computedCacheControl = $this->parseCacheControl($computed);
}
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
parent::delete($key);
if ('cache-control' === strtr(strtolower($key), '_', '-')) {
$this->computedCacheControl = array();
}
}
/**
* {@inheritdoc}
*/
public function setCookie($name, $value, $domain = null, $expires = null, $path = '/', $secure = false, $httponly = true)
{
$this->validateCookieName($name, $value);
$cookie = sprintf('%s=%s', $name, urlencode($value));
if (null !== $expires) {
if (is_numeric($expires)) {
$expires = (int) $expires;
} elseif ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} else {
$expires = strtotime($expires);
if (false === $expires || -1 == $expires) {
throw new \InvalidArgumentException(sprintf('The "expires" cookie parameter is not valid.', $expires));
}
}
$cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), 0, -5);
}
if ($domain) {
$cookie .= '; domain='.$domain;
}
$cookie .= '; path='.$path;
if ($secure) {
$cookie .= '; secure';
}
if ($httponly) {
$cookie .= '; httponly';
}
$this->set('Set-Cookie', $cookie, false);
}
/**
* {@inheritdoc}
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl);
}
/**
* {@inheritdoc}
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache';
}
if (!$this->cacheControl) {
// conservative by default
return 'private, max-age=0, must-revalidate';
}
$header = $this->getCacheControlHeader();
if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
return $header;
}
// public if s-maxage is defined, private otherwise
if (!isset($this->cacheControl['s-maxage'])) {
return $header.', private';
}
return $header;
}
}

View File

@ -49,7 +49,6 @@ class NativeSessionStorage implements SessionStorageInterface
'domain' => $cookieDefaults['domain'],
'secure' => $cookieDefaults['secure'],
'httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false,
'cache_limiter' => 'none',
), $options);
session_name($this->options['name']);
@ -72,9 +71,8 @@ class NativeSessionStorage implements SessionStorageInterface
$this->options['httponly']
);
if (null !== $this->options['cache_limiter']) {
session_cache_limiter($this->options['cache_limiter']);
}
// disable native cache limiter as this is managed by HeaderBag directly
session_cache_limiter(false);
if (!ini_get('session.use_cookies') && $this->options['id'] && $this->options['id'] != session_id()) {
session_id($this->options['id']);

View File

@ -343,9 +343,9 @@ class Cache implements HttpKernelInterface
$response = $this->forward($subRequest);
if ($this->isPrivateRequest($request) && !$response->headers->getCacheControl()->isPublic()) {
if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
$response->setPrivate(true);
} elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControl()->mustRevalidate()) {
} elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) {
$response->setTtl($this->options['default_ttl']);
}
@ -377,7 +377,7 @@ class Cache implements HttpKernelInterface
// we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC
if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) {
if (null === $age = $entry->headers->getCacheControl()->getStaleIfError()) {
if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) {
$age = $this->options['stale_if_error'];
}
@ -407,7 +407,7 @@ class Cache implements HttpKernelInterface
return $this->lock($request, $entry);
}
if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControl()->getMaxAge()) {
if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) {
return $maxAge > 0 && $maxAge >= $entry->getAge();
}
@ -430,7 +430,7 @@ class Cache implements HttpKernelInterface
// there is already another process calling the backend
if (true !== $lock) {
// check if we can serve the stale entry
if (null === $age = $entry->headers->getCacheControl()->getStaleWhileRevalidate()) {
if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) {
$age = $this->options['stale_while_revalidate'];
}

View File

@ -242,7 +242,7 @@ class Store
}
foreach (preg_split('/[\s,]+/', $vary) as $header) {
$key = HeaderBag::normalizeHeaderName($header);
$key = strtr(strtolower($header), '_', '-');
$v1 = isset($env1[$key]) ? $env1[$key] : null;
$v2 = isset($env2[$key]) ? $env2[$key] : null;
if ($v1 !== $v2) {

View File

@ -75,7 +75,7 @@ class RequestDataCollector extends DataCollector
public function getResponseHeaders()
{
return new HeaderBag($this->data['response_headers']);
return new ResponseHeaderBag($this->data['response_headers']);
}
public function getSessionAttributes()

View File

@ -1,4 +1,5 @@
<?php
/*
* This file is part of the Symfony package.
*
@ -20,24 +21,7 @@ class HeaderBagTest extends \PHPUnit_Framework_TestCase
public function testConstructor()
{
$bag = new HeaderBag(array('foo' => 'bar'));
$this->assertTrue( $bag->has('foo'));
try {
$bag = new HeaderBag(array('foo' => 'bar'), 'nope');
$this->assertFalse(TRUE,'nope is not a valid type'); // --> enfore request or response
} catch ( \InvalidArgumentException $e) {
// ignore
}
try {
$bag = new HeaderBag(array('foo' => 'bar'), 'request');
} catch ( \Exception $e) {
$this->assertFalse(TRUE,'request should be a valid type'); // --> enforce request or response
}
try {
$bag = new HeaderBag(array('foo' => 'bar'), 'response');
} catch ( \Exception $e) {
$this->assertFalse(TRUE,'response should be a valid type'); // --> enforce request or response
}
$this->assertTrue($bag->has('foo'));
}
/**
@ -64,7 +48,6 @@ class HeaderBagTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
}
/**
* @covers Symfony\Component\HttpFoundation\HeaderBag::get
*/
@ -103,5 +86,45 @@ class HeaderBagTest extends \PHPUnit_Framework_TestCase
$this->assertFalse( $bag->contains('foo', 'nope'), '->contains unknown value');
}
public function testCacheControlDirectiveAccessors()
{
$bag = new HeaderBag();
$bag->addCacheControlDirective('public');
$this->assertTrue($bag->hasCacheControlDirective('public'));
$this->assertEquals(true, $bag->getCacheControlDirective('public'));
$this->assertEquals('public', $bag->get('cache-control'));
$bag->addCacheControlDirective('max-age', 10);
$this->assertTrue($bag->hasCacheControlDirective('max-age'));
$this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
$this->assertEquals('max-age=10, public', $bag->get('cache-control'));
$bag->removeCacheControlDirective('max-age');
$this->assertFalse($bag->hasCacheControlDirective('max-age'));
}
public function testCacheControlDirectiveParsing()
{
$bag = new HeaderBag(array('cache-control' => 'public, max-age=10'));
$this->assertTrue($bag->hasCacheControlDirective('public'));
$this->assertEquals(true, $bag->getCacheControlDirective('public'));
$this->assertTrue($bag->hasCacheControlDirective('max-age'));
$this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
$bag->addCacheControlDirective('s-maxage', 100);
$this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control'));
}
public function testCacheControlDirectiveOverrideWithReplace()
{
$bag = new HeaderBag(array('cache-control' => 'private, max-age=100'));
$bag->replace(array('cache-control' => 'public, max-age=10'));
$this->assertTrue($bag->hasCacheControlDirective('public'));
$this->assertEquals(true, $bag->getCacheControlDirective('public'));
$this->assertTrue($bag->hasCacheControlDirective('max-age'));
$this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
{
public function testCacheControlHeader()
{
$bag = new ResponseHeaderBag(array(), 'response');
$this->assertEquals('no-cache', $bag->get('Cache-Control'));
$this->assertTrue($bag->hasCacheControlDirective('no-cache'));
$bag = new ResponseHeaderBag(array('Cache-Control' => 'public'), 'response');
$this->assertEquals('public', $bag->get('Cache-Control'));
$this->assertTrue($bag->hasCacheControlDirective('public'));
$bag = new ResponseHeaderBag(array('ETag' => 'abcde'), 'response');
$this->assertEquals('private, max-age=0, must-revalidate', $bag->get('Cache-Control'));
$this->assertTrue($bag->hasCacheControlDirective('private'));
$this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
$this->assertEquals(0, $bag->getCacheControlDirective('max-age'));
$bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde'), 'response');
$this->assertEquals('private, max-age=0, must-revalidate', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde'), 'response');
$this->assertEquals('private, max-age=0, must-revalidate', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100'), 'response');
$this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100'), 'response');
$this->assertEquals('s-maxage=100', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100'), 'response');
$this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100'), 'response');
$this->assertEquals('max-age=100, public', $bag->get('Cache-Control'));
}
}

View File

@ -65,16 +65,16 @@ class ResponseTest extends \PHPUnit_Framework_TestCase
{
$response = new Response();
$response->headers->set('Cache-Control', 'max-age=100');
$response->setPrivate(true);
$this->assertEquals(100, $response->headers->getCacheControl()->getMaxAge(), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertTrue($response->headers->getCacheControl()->isPrivate(), '->isPrivate() adds the private Cache-Control directive when set to true');
$response->setPrivate();
$this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
$response = new Response();
$response->headers->set('Cache-Control', 'public, max-age=100');
$response->setPrivate(true);
$this->assertEquals(100, $response->headers->getCacheControl()->getMaxAge(), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertTrue($response->headers->getCacheControl()->isPrivate(), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertFalse($response->headers->getCacheControl()->isPublic(), '->isPrivate() removes the public Cache-Control directive');
$response->setPrivate();
$this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
$this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive');
}
public function testExpire()

View File

@ -95,7 +95,7 @@ class CacheTest extends CacheTestCase
{
$time = new \DateTime();
$this->setNextResponse(200, array('Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World');
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World');
$this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
$this->assertHttpKernelIsCalled();
@ -109,7 +109,7 @@ class CacheTest extends CacheTestCase
public function testRespondsWith304WhenIfNoneMatchMatchesETag()
{
$this->setNextResponse(200, array('ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World');
$this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World');
$this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345'));
$this->assertHttpKernelIsCalled();
@ -201,7 +201,7 @@ class CacheTest extends CacheTestCase
{
$time = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
$this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache'));
$this->assertHttpKernelIsCalled();
@ -213,7 +213,7 @@ class CacheTest extends CacheTestCase
{
$count = 0;
$this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count)
$this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count)
{
++$count;
$response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
@ -241,7 +241,7 @@ class CacheTest extends CacheTestCase
{
$count = 0;
$this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count)
$this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count)
{
++$count;
$response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
@ -276,7 +276,7 @@ class CacheTest extends CacheTestCase
$this->setNextResponse(200, array(), '', function ($request, $response) use (&$count)
{
++$count;
$response->headers->set('Cache-Control', 'max-age=10000');
$response->headers->set('Cache-Control', 'public, max-age=10000');
$response->setETag($count);
$response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
});
@ -307,7 +307,7 @@ class CacheTest extends CacheTestCase
$this->setNextResponse(200, array(), '', function ($request, $response) use (&$count)
{
++$count;
$response->headers->set('Cache-Control', 'max-age=10000');
$response->headers->set('Cache-Control', 'public, max-age=10000');
$response->setETag($count);
$response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
});
@ -341,7 +341,7 @@ class CacheTest extends CacheTestCase
public function testFetchesResponseFromBackendWhenCacheMisses()
{
$time = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
$this->request('GET', '/');
$this->assertEquals(200, $this->response->getStatusCode());
@ -384,7 +384,7 @@ class CacheTest extends CacheTestCase
public function testCachesResponesWithExplicitNoCacheDirective()
{
$time = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-cache'));
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache'));
$this->request('GET', '/');
$this->assertTraceContains('store');
@ -394,7 +394,7 @@ class CacheTest extends CacheTestCase
public function testCachesResponsesWithAnExpirationHeader()
{
$time = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
$this->request('GET', '/');
$this->assertEquals(200, $this->response->getStatusCode());
@ -410,7 +410,7 @@ class CacheTest extends CacheTestCase
public function testCachesResponsesWithAMaxAgeDirective()
{
$this->setNextResponse(200, array('Cache-Control' => 'max-age=5'));
$this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5'));
$this->request('GET', '/');
$this->assertEquals(200, $this->response->getStatusCode());
@ -443,7 +443,7 @@ class CacheTest extends CacheTestCase
public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation()
{
$time = \DateTime::createFromFormat('U', time());
$this->setNextResponse(200, array('Last-Modified' => $time->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822)));
$this->request('GET', '/');
$this->assertEquals(200, $this->response->getStatusCode());
@ -454,7 +454,7 @@ class CacheTest extends CacheTestCase
public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation()
{
$this->setNextResponse(200, array('ETag' => '"123456"'));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"'));
$this->request('GET', '/');
$this->assertEquals(200, $this->response->getStatusCode());
@ -467,7 +467,7 @@ class CacheTest extends CacheTestCase
{
$time1 = \DateTime::createFromFormat('U', time() - 5);
$time2 = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822)));
$this->request('GET', '/');
$this->assertHttpKernelIsCalled();
@ -491,7 +491,7 @@ class CacheTest extends CacheTestCase
public function testHitsCachedResponseWithMaxAgeDirective()
{
$time = \DateTime::createFromFormat('U', time() - 5);
$this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'max-age=10'));
$this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10'));
$this->request('GET', '/');
$this->assertHttpKernelIsCalled();
@ -574,7 +574,7 @@ class CacheTest extends CacheTestCase
public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent()
{
$time = \DateTime::createFromFormat('U', time() + 5);
$this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822)));
$this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
// build initial request
$this->request('GET', '/');
@ -613,6 +613,7 @@ class CacheTest extends CacheTestCase
$time = \DateTime::createFromFormat('U', time());
$this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time)
{
$response->headers->set('Cache-Control', 'public');
$response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) {
$response->setStatusCode(304);
@ -649,6 +650,7 @@ class CacheTest extends CacheTestCase
{
$this->setNextResponse(200, array(), 'Hello World', function ($request, $response)
{
$response->headers->set('Cache-Control', 'public');
$response->headers->set('ETag', '"12345"');
if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) {
$response->setStatusCode(304);
@ -739,7 +741,7 @@ class CacheTest extends CacheTestCase
$that = $this;
$this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that)
{
$response->headers->set('Cache-Control', 'max-age=10');
$response->headers->set('Cache-Control', 'public, max-age=10');
$response->setContent('Hello World');
$response->setStatusCode(200);
$that->assertNotEquals('HEAD', $request->getMethod());
@ -811,7 +813,7 @@ class CacheTest extends CacheTestCase
$this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count)
{
$response->headers->set('Vary', 'Accept User-Agent Foo');
$response->headers->set('Cache-Control', 'max-age=10');
$response->headers->set('Cache-Control', 'public, max-age=10');
$response->headers->set('X-Response-Count', ++$count);
$response->setContent($request->headers->get('USER_AGENT'));
});
@ -836,7 +838,7 @@ class CacheTest extends CacheTestCase
$this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count)
{
$response->headers->set('Vary', 'Accept User-Agent Foo');
$response->headers->set('Cache-Control', 'max-age=10');
$response->headers->set('Cache-Control', 'public, max-age=10');
$response->headers->set('X-Response-Count', ++$count);
$response->setContent($request->headers->get('USER_AGENT'));
});

View File

@ -16,7 +16,6 @@ require_once __DIR__.'/CacheTestCase.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Cache\Store;
use Symfony\Tests\Component\HttpKernel\Cache\CacheTestCase;
class CacheStoreTest extends \PHPUnit_Framework_TestCase
{