Skip to content

Commit 02f12cc

Browse files
committed
Add ability to not only cache HTTP 200 and GET method
- Add pages with response 200, 203, 300, 301, 302, 404, 410 to cache. - Add to cache response from HEAD method request.
1 parent ca340da commit 02f12cc

10 files changed

+204
-23
lines changed

lib/controller/sfController.class.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ public function getActionStack()
331331
* @return int One of the following:
332332
* - sfView::RENDER_CLIENT
333333
* - sfView::RENDER_VAR
334+
* - sfView::RENDER_NONE
335+
* - sfView::RENDER_REDIRECTION
334336
*/
335337
public function getRenderMode()
336338
{
@@ -472,18 +474,24 @@ public function getPresentationFor($module, $action, $viewName = null)
472474
* - sfView::RENDER_CLIENT
473475
* - sfView::RENDER_VAR
474476
* - sfView::RENDER_NONE
477+
* - sfView::RENDER_REDIRECTION
475478
*
476479
* @return void
477480
*
478481
* @throws sfRenderException If an invalid render mode has been set
479482
*/
480483
public function setRenderMode($mode)
481484
{
482-
if ($mode == sfView::RENDER_CLIENT || $mode == sfView::RENDER_VAR || $mode == sfView::RENDER_NONE)
485+
switch ($mode)
483486
{
484-
$this->renderMode = $mode;
485-
486-
return;
487+
case sfView::RENDER_CLIENT:
488+
case sfView::RENDER_VAR:
489+
case sfView::RENDER_NONE:
490+
case sfView::RENDER_REDIRECTION:
491+
$this->renderMode = $mode;
492+
return;
493+
default:
494+
break;
487495
}
488496

489497
// invalid rendering mode type

lib/controller/sfWebController.class.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ public function redirect($url, $delay = 0, $statusCode = 302)
205205
}
206206

207207
$response->setContent(sprintf('<html><head><meta http-equiv="refresh" content="%d;url=%s"/></head></html>', $delay, htmlspecialchars($url, ENT_QUOTES, sfConfig::get('sf_charset'))));
208-
$response->send();
208+
209+
$this->setRenderMode(sfView::RENDER_REDIRECTION);
209210
}
210211
}

lib/filter/sfCacheFilter.class.php

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ class sfCacheFilter extends sfFilter
2525
$routing = null,
2626
$cache = array();
2727

28+
/**
29+
* Responses with its status codes may safely be kept in a shared (surrogate) cache.
30+
*
31+
* Put status codes as key in ordder to be able to use `isset()`.
32+
*
33+
* @var array
34+
*/
35+
private $cacheableStatusCodes = array(
36+
200 => true,
37+
203 => true,
38+
300 => true,
39+
301 => true,
40+
302 => true,
41+
404 => true,
42+
410 => true,
43+
);
44+
2845
/**
2946
* Initializes this Filter.
3047
*
@@ -60,12 +77,30 @@ public function execute($filterChain)
6077
return;
6178
}
6279

80+
$exception = null;
81+
6382
if ($this->executeBeforeExecution())
6483
{
65-
$filterChain->execute();
84+
try
85+
{
86+
// execute next filter
87+
$filterChain->execute();
88+
}
89+
catch (sfStopException $exception)
90+
{
91+
if (sfView::RENDER_REDIRECTION !== $this->context->getController()->getRenderMode())
92+
{
93+
throw $exception;
94+
}
95+
}
6696
}
6797

6898
$this->executeBeforeRendering();
99+
100+
if (null !== $exception)
101+
{
102+
throw $exception;
103+
}
69104
}
70105

71106
public function executeBeforeExecution()
@@ -102,8 +137,7 @@ public function executeBeforeExecution()
102137
*/
103138
public function executeBeforeRendering()
104139
{
105-
// cache only 200 HTTP status
106-
if (200 != $this->response->getStatusCode())
140+
if (!$this->isCacheableResponse($this->response))
107141
{
108142
return;
109143
}
@@ -168,6 +202,9 @@ protected function setCacheValidation($uri)
168202
// don't add cache validation (Last-Modified) if
169203
// * the client lifetime is set (cache.yml)
170204
// * the response already has a Last-Modified header
205+
//
206+
// TODO The second argument is kept to avoid BC break but
207+
// there is not test about it.
171208
if ($this->cacheManager->getClientLifeTime($uri, 'page'))
172209
{
173210
return;
@@ -224,4 +261,50 @@ protected function checkCacheValidation()
224261
}
225262
}
226263
}
264+
265+
/**
266+
* Returns true if the response may safely be kept in a shared (surrogate) cache.
267+
*
268+
* Responses marked "private" with an explicit Cache-Control directive are
269+
* considered uncacheable.
270+
*
271+
* Responses with neither a freshness lifetime (Expires, max-age) nor cache
272+
* validator (Last-Modified, ETag) are considered uncacheable because there is
273+
* no way to tell when or how to remove them from the cache.
274+
*
275+
* Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
276+
* for example "status codes that are defined as cacheable by default [...]
277+
* can be reused by a cache with heuristic expiration unless otherwise indicated"
278+
* (https://tools.ietf.org/html/rfc7231#section-6.1)
279+
*
280+
* @param sfWebResponse $response
281+
*
282+
* @return bool
283+
*
284+
* @see https://github.com/symfony/symfony/blob/v4.1.6/src/Symfony/Component/HttpFoundation/Response.php#L523
285+
*/
286+
protected function isCacheableResponse($response)
287+
{
288+
if (!isset($this->cacheableStatusCodes[$response->getStatusCode()]))
289+
{
290+
return false;
291+
}
292+
293+
// Maybe add getCacheControlHttpHeader to sfWebResponse.
294+
// TODO Add a test for that.
295+
$cacheControl = $response->getHttpHeader('Cache-Control', '');
296+
$cacheControlDirectives = explode(', ', $cacheControl);
297+
$privateDirectives = array('no-store', 'private');
298+
299+
if ($privateDirectives !== array_diff($privateDirectives, $cacheControlDirectives))
300+
{
301+
return false;
302+
}
303+
304+
// Skip $this->isValidateable() || $this->isFresh() because cache control headers
305+
// are sets after.
306+
// FIXME But implement it because the cache control can be set on the controller.
307+
308+
return true;
309+
}
227310
}

lib/filter/sfExecutionFilter.class.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,29 @@ protected function executeAction($actionInstance)
9090
{
9191
// execute the action
9292
$actionInstance->preExecute();
93-
$viewName = $actionInstance->execute($this->context->getRequest());
93+
94+
try {
95+
$viewName = $actionInstance->execute($this->context->getRequest());
96+
} catch (sfStopException $e) {
97+
if (!sfConfig::get('sf_cache')) {
98+
throw $e;
99+
}
100+
101+
if (sfView::RENDER_REDIRECTION === $this->context->getController()->getRenderMode())
102+
{
103+
$viewCache = $this->context->getViewCacheManager();
104+
$response = $this->context->getResponse();
105+
$uri = $viewCache->getCurrentCacheKey();
106+
107+
if (null !== $uri)
108+
{
109+
$viewCache->setActionCache($uri, $response->getContent(), false);
110+
}
111+
}
112+
113+
throw $e;
114+
}
115+
94116
$actionInstance->postExecute();
95117

96118
return null === $viewName ? sfView::SUCCESS : $viewName;

lib/filter/sfRenderingFilter.class.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,22 @@ class sfRenderingFilter extends sfFilter
2929
*/
3030
public function execute($filterChain)
3131
{
32+
$controller = $this->context->getController();
33+
$exception = null;
34+
3235
// execute next filter
33-
$filterChain->execute();
36+
try
37+
{
38+
$filterChain->execute();
39+
}
40+
catch (sfStopException $exception)
41+
{
42+
// Send the response when stop the execution for a redirection.
43+
if (sfView::RENDER_REDIRECTION !== $controller->getRenderMode())
44+
{
45+
throw $exception;
46+
}
47+
}
3448

3549
// get response object
3650
$response = $this->context->getResponse();
@@ -46,9 +60,15 @@ public function execute($filterChain)
4660
}
4761

4862
// send headers + content
49-
if (sfView::RENDER_VAR != $this->context->getController()->getRenderMode())
63+
if (sfView::RENDER_VAR != $controller->getRenderMode())
64+
{
65+
$response->send();
66+
}
67+
68+
// Re-throw the exception to keep the encapsulation.
69+
if (null !== $exception)
5070
{
51-
$response->send();
71+
throw $exception;
5272
}
5373
}
5474
}

lib/response/sfWebResponse.class.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,9 @@ public function clearHttpHeaders()
843843
public function copyProperties(sfWebResponse $response)
844844
{
845845
$this->options = $response->getOptions();
846+
$this->statusCode = $response->getStatusCode();
847+
$this->statusText = $response->getStatusText();
848+
$this->headerOnly = $response->isHeaderOnly();
846849
$this->headers = $response->getHttpHeaders();
847850
$this->metas = $response->getMetas();
848851
$this->httpMetas = $response->getHttpMetas();

lib/test/sfTesterViewCache.class.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,26 @@ public function isUriCached($uri, $boolean, $with_layout = false)
106106
// check that the content is ok in cache
107107
if ($boolean)
108108
{
109+
$fullContent = $this->response->getContent();
110+
$withContent = !$this->response->isHeaderOnly() || '' !== $fullContent;
111+
109112
if (!$ret)
110113
{
111114
$this->tester->fail('content in cache is ok');
112115
}
113-
else if ($with_layout)
116+
else if ($with_layout && $withContent)
114117
{
115118
$response = unserialize($cacheManager->get($uri));
116119
$content = $response->getContent();
117-
$this->tester->ok($content == $this->response->getContent(), 'content in cache is ok');
120+
121+
$this->tester->is($content, $fullContent, 'content in cache with layout is ok');
118122
}
119-
else
123+
else if ($withContent)
120124
{
121125
$ret = unserialize($cacheManager->get($uri));
122126
$content = $ret['content'];
123-
$this->tester->ok(false !== strpos($this->response->getContent(), $content), 'content in cache is ok');
127+
128+
$this->tester->ok(false !== strpos($fullContent, $content), 'content in cache without layout is ok');
124129
}
125130
}
126131
}

lib/util/sfBrowser.class.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,28 @@ class sfFakeRenderingFilter extends sfFilter
162162
{
163163
public function execute($filterChain)
164164
{
165-
$filterChain->execute();
165+
$controller = $this->context->getController();
166+
$exception = null;
167+
168+
try
169+
{
170+
$filterChain->execute();
171+
}
172+
catch (sfStopException $exception)
173+
{
174+
// Send the response when stop the execution for a redirection.
175+
if (sfView::RENDER_REDIRECTION !== $controller->getRenderMode())
176+
{
177+
throw $exception;
178+
}
179+
}
166180

167181
$this->context->getResponse()->sendContent();
182+
183+
// Re-throw the exception to keep the encapsulation.
184+
if (null !== $exception)
185+
{
186+
throw $exception;
187+
}
168188
}
169189
}

lib/view/sfView.class.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ abstract class sfView
6262
*/
6363
const RENDER_VAR = 4;
6464

65+
/**
66+
* Render the presentation as redirection.
67+
*/
68+
const RENDER_REDIRECTION = 16;
69+
6570
/**
6671
* Skip view rendering but output http headers
6772
*/

lib/view/sfViewCacheManager.class.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ public function generateCacheKey($internalUri, $hostName = '', $vary = '', $cont
182182
$cacheKey = '/'.$hostNamePart.'/'.ltrim($cacheKey, '/');
183183
}
184184

185+
// BC layer to avoid invalidate all cache.
186+
if (sfRequest::GET !== $method = $this->request->getMethod())
187+
{
188+
$cacheKey = '/'.$this->request->getMethod().'/'.ltrim($cacheKey, '/');
189+
}
190+
185191
// normalize to a leading slash
186192
if (0 !== strpos($cacheKey, '/'))
187193
{
@@ -447,8 +453,10 @@ protected function getCacheConfig($internalUri, $key, $defaultValue = null)
447453
*/
448454
public function isCacheable($internalUri)
449455
{
450-
if ($this->request instanceof sfWebRequest && !$this->request->isMethod(sfRequest::GET))
451-
{
456+
if (
457+
$this->request instanceof sfWebRequest
458+
&& !in_array(strtoupper($this->request->getMethod()), array(sfRequest::GET, sfRequest::HEAD), true)
459+
) {
452460
return false;
453461
}
454462

@@ -485,8 +493,10 @@ public function isCacheable($internalUri)
485493
*/
486494
public function isActionCacheable($moduleName, $actionName)
487495
{
488-
if ($this->request instanceof sfWebRequest && !$this->request->isMethod(sfRequest::GET))
489-
{
496+
if (
497+
$this->request instanceof sfWebRequest
498+
&& !in_array(strtoupper($this->request->getMethod()), array(sfRequest::GET, sfRequest::HEAD), true)
499+
) {
490500
return false;
491501
}
492502

@@ -936,9 +946,13 @@ public function setActionCache($uri, $content, $decoratorTemplate)
936946
*/
937947
public function setPageCache($uri)
938948
{
939-
if (sfView::RENDER_CLIENT != $this->controller->getRenderMode())
949+
switch ($this->controller->getRenderMode())
940950
{
941-
return;
951+
case sfView::RENDER_CLIENT:
952+
case sfView::RENDER_REDIRECTION:
953+
break;
954+
default:
955+
return;
942956
}
943957

944958
// save content in cache

0 commit comments

Comments
 (0)