From d9987c96db8610bdc09b119f197efc6a98c3810f Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 6 Nov 2011 23:51:16 -0430 Subject: [PATCH] Implementing sharable() and maxAge() in CakeResponse for a finer grain and easier control of cache headers --- lib/Cake/Network/CakeResponse.php | 81 ++++++++++++++++++- .../Test/Case/Network/CakeResponseTest.php | 34 ++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Network/CakeResponse.php b/lib/Cake/Network/CakeResponse.php index e2545e161..17d59580d 100644 --- a/lib/Cake/Network/CakeResponse.php +++ b/lib/Cake/Network/CakeResponse.php @@ -311,6 +311,14 @@ class CakeResponse { */ protected $_charset = 'UTF-8'; +/** + * Holds all the cache directives that will be converted + * into headers when sending the request + * + * @var string + */ + protected $_cacheDirectives = array(); + /** * Class constructor * @@ -675,14 +683,81 @@ class CakeResponse { $time = strtotime($time); } $this->header(array( - 'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT', - 'Cache-Control' => 'public, max-age=' . ($time - time()), - 'Pragma' => 'cache' + 'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT' )); $this->modified($since); $this->expires($time); + $this->sharable(true); + $this->maxAge($time - time()); } +/** + * Sets whether a response is eligible to be cached by intermediate proxies + * This method controls the `public` or `private` directive in the Cache-Control + * header + * + * @param boolean $public if set to true, the Cache-Control header will be set as public + * if set to false, the response will be set to private + * if no value is provided, it will return whether the response is sharable or not + * @return boolean + */ + public function sharable($public = null) { + if ($public === null) { + $public = array_key_exists('public', $this->_cacheDirectives); + $private = array_key_exists('private', $this->_cacheDirectives); + $noCache = array_key_exists('no-cache', $this->_cacheDirectives); + if (!$public && !$private && !$noCache) { + return null; + } + $sharable = $public || ! ($private || $noCache); + return $sharable; + } + if ($public) { + $this->_cacheDirectives['public'] = null; + unset($this->_cacheDirectives['private']); + } else { + $this->_cacheDirectives['private'] = null; + unset($this->_cacheDirectives['public']); + } + $this->_setCacheControl(); + return (bool) $public; + } + +/** + * Sets the Cache-Control max-age directive. + * The max-age is the number of seconds after which the response should no longer be considered + * a good candidate to be fetched from the local (client) cache. + * If called with no parameters, this function will return the current max-age value if any + * + * @param int $seconds + * @return int + */ + public function maxAge($seconds = null) { + if ($seconds !== null) { + $this->_cacheDirectives['max-age'] = $seconds; + $this->_setCacheControl(); + } + if (isset($this->_cacheDirectives['max-age'])) { + return $this->_cacheDirectives['max-age']; + } + return null; + } + +/** + * Helper method to generate a valid Cache-Control header from the options set + * in other methods + * + * @return void + */ + protected function _setCacheControl() { + $control = ''; + foreach ($this->_cacheDirectives as $key => $val) { + $control .= is_null($val) ? $key : sprintf('%s=%s', $key, $val); + $control .= ', '; + } + $control = rtrim($control, ', '); + $this->header('Cache-Control', $control); + } /** * Sets the Expires header for the response by taking an expiration time diff --git a/lib/Cake/Test/Case/Network/CakeResponseTest.php b/lib/Cake/Test/Case/Network/CakeResponseTest.php index e9522db67..d39ff8982 100644 --- a/lib/Cake/Test/Case/Network/CakeResponseTest.php +++ b/lib/Cake/Test/Case/Network/CakeResponseTest.php @@ -635,4 +635,38 @@ class CakeResponseTest extends CakeTestCase { ->method('_sendHeader')->with('Last-Modified', $time->format('D, j M Y H:i:s') . ' GMT'); $response->send(); } + + public function testSharable() { + $response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent')); + $this->assertNull($response->sharable()); + $response->sharable(true); + $headers = $response->header(); + $this->assertEquals('public', $headers['Cache-Control']); + $response->expects($this->at(1)) + ->method('_sendHeader')->with('Cache-Control', 'public'); + $response->send(); + + + $response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent')); + $response->sharable(false); + $headers = $response->header(); + $this->assertEquals('private', $headers['Cache-Control']); + $response->expects($this->at(1)) + ->method('_sendHeader')->with('Cache-Control', 'private'); + $response->send(); + + $response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent')); + $response->sharable(true); + $headers = $response->header(); + $this->assertEquals('public', $headers['Cache-Control']); + $response->sharable(false); + $headers = $response->header(); + $this->assertEquals('private', $headers['Cache-Control']); + $response->expects($this->at(1)) + ->method('_sendHeader')->with('Cache-Control', 'private'); + $response->send(); + $this->assertFalse($response->sharable()); + $response->sharable(true); + $this->assertTrue($response->sharable()); + } }