Skip to content

Commit

Permalink
Merge pull request #5433 from cakephp/3.0-cell-cache
Browse files Browse the repository at this point in the history
3.0 cell cache
  • Loading branch information
lorenzo committed Dec 18, 2014
2 parents 813fa85 + ea8144b commit 1c4aa96
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 40 deletions.
67 changes: 57 additions & 10 deletions src/View/Cell.php
Expand Up @@ -115,6 +115,13 @@ abstract class Cell {
*/
protected $_validCellOptions = [];

/**
* Caching setup.
*
* @var array|bool
*/
protected $_cache = false;

/**
* Constructor.
*
Expand All @@ -135,6 +142,9 @@ public function __construct(Request $request = null, Response $response = null,
$this->{$var} = $cellOptions[$var];
}
}
if (!empty($cellOptions['cache'])) {
$this->_cache = $cellOptions['cache'];
}
}

/**
Expand All @@ -152,21 +162,58 @@ public function render($template = null) {
if ($template === null) {
$template = $this->template;
}

$this->View = null;
$this->getView();

$this->View->layout = false;
$className = explode('\\', get_class($this));
$className = array_pop($className);
$name = substr($className, 0, strpos($className, 'Cell'));
$this->View->subDir = 'Cell' . DS . $name;

try {
return $this->View->render($template);
} catch (MissingTemplateException $e) {
throw new MissingCellViewException(['file' => $template, 'name' => $name]);
$cache = [];
if ($this->_cache) {
$cache = $this->_cacheConfig($template);
}

$render = function () use ($template) {
$className = explode('\\', get_class($this));
$className = array_pop($className);
$name = substr($className, 0, strpos($className, 'Cell'));
$this->View->subDir = 'Cell' . DS . $name;

try {
return $this->View->render($template);
} catch (MissingTemplateException $e) {
throw new MissingCellViewException(['file' => $template, 'name' => $name]);
}
};

if ($cache) {
return $this->View->cache(function () use ($render) {
echo $render();
}, $cache);
}
return $render();
}

/**
* Generate the cache key to use for this cell.
*
* If the key is undefined, the cell class and template will be used.
*
* @param string $template The template being rendered.
* @return array The cache configuration.
*/
protected function _cacheConfig($template) {
if (empty($this->_cache)) {
return [];
}
$key = 'cell_' . Inflector::underscore(get_class($this)) . '_' . $template;
$key = str_replace('\\', '_', $key);
$default = [
'config' => 'default',
'key' => $key
];
if ($this->_cache === true) {
return $default;
}
return $this->_cache + $default;
}

/**
Expand Down
79 changes: 51 additions & 28 deletions src/View/View.php
Expand Up @@ -29,6 +29,7 @@
use Cake\View\ViewVarsTrait;
use InvalidArgumentException;
use LogicException;
use RuntimeException;

/**
* View, the V in the MVC triad. View interacts with Helpers and view variables passed
Expand Down Expand Up @@ -206,15 +207,6 @@ class View {
*/
public $elementCache = 'default';

/**
* Element cache settings
*
* @var array
* @see View::_elementCache();
* @see View::_renderElement
*/
public $elementCacheSettings = array();

/**
* List of variables to collect from the associated controller.
*
Expand Down Expand Up @@ -360,18 +352,17 @@ public function initialize() {
* is false.
*/
public function element($name, array $data = array(), array $options = array()) {
if (!isset($options['callbacks'])) {
$options['callbacks'] = false;
}

$options += ['callbacks' => false, 'cache' => null];
if (isset($options['cache'])) {
$contents = $this->_elementCache($name, $data, $options);
if ($contents !== false) {
return $contents;
}
$options['cache'] = $this->_elementCache($name, $data, $options);
}

$file = $this->_getElementFilename($name);
if ($file && $options['cache']) {
return $this->cache(function () use ($file, $data, $options) {
echo $this->_renderElement($file, $data, $options);
}, $options['cache']);
}
if ($file) {
return $this->_renderElement($file, $data, $options);
}
Expand All @@ -384,6 +375,37 @@ public function element($name, array $data = array(), array $options = array())
}
}

/**
* Create a cached block of view logic.
*
* This allows you to cache a block of view output into the cache
* defined in `elementCache`.
*
* This method will attempt to read the cache first. If the cache
* is empty, the $block will be run and the output stored.
*
* @param callable $block The block of code that you want to cache the output of.
* @param array $options The options defining the cache key etc.
* @return string The rendered content.
* @throws \RuntimeException When $options is lacking a 'key' option.
*/
public function cache(callable $block, array $options = []) {
$options += ['key' => '', 'config' => $this->elementCache];
if (empty($options['key'])) {
throw new RuntimeException('Cannot cache content with an empty key');
}
$result = Cache::read($options['key'], $options['config']);
if ($result) {
return $result;
}
ob_start();
$block();
$result = ob_get_clean();

Cache::write($options['key'], $result, $options['config']);
return $result;
}

/**
* Checks if an element exists
*
Expand Down Expand Up @@ -1010,12 +1032,12 @@ protected function _paths($plugin = null, $cached = true) {
}

/**
* Checks if an element is cached and returns the cached data if present
* Generate the cache configuration options for an element.
*
* @param string $name Element name
* @param array $data Data
* @param array $options Element options
* @return string|null
* @return array Element Cache configuration.
*/
protected function _elementCache($name, $data, $options) {
$plugin = null;
Expand All @@ -1025,20 +1047,24 @@ protected function _elementCache($name, $data, $options) {
if ($plugin) {
$underscored = Inflector::underscore($plugin);
}
$keys = array_merge(array($underscored, $name), array_keys($options), array_keys($data));
$this->elementCacheSettings = array(
$keys = array_merge(
array($underscored, $name),
array_keys($options),
array_keys($data)
);
$config = array(
'config' => $this->elementCache,
'key' => implode('_', $keys)
);
if (is_array($options['cache'])) {
$defaults = array(
'config' => $this->elementCache,
'key' => $this->elementCacheSettings['key']
'key' => $config['key']
);
$this->elementCacheSettings = $options['cache'] + $defaults;
$config = $options['cache'] + $defaults;
}
$this->elementCacheSettings['key'] = 'element_' . $this->elementCacheSettings['key'];
return Cache::read($this->elementCacheSettings['key'], $this->elementCacheSettings['config']);
$config['key'] = 'element_' . $config['key'];
return $config;
}

/**
Expand Down Expand Up @@ -1070,9 +1096,6 @@ protected function _renderElement($file, $data, $options) {
$this->_currentType = $restore;
$this->_current = $current;

if (isset($options['cache'])) {
Cache::write($this->elementCacheSettings['key'], $element, $this->elementCacheSettings['config']);
}
return $element;
}

Expand Down
1 change: 0 additions & 1 deletion tests/TestCase/Error/DebuggerTest.php
Expand Up @@ -346,7 +346,6 @@ public function testExportVar() {
request => object(Cake\Network\Request) {}
response => object(Cake\Network\Response) {}
elementCache => 'default'
elementCacheSettings => []
viewVars => []
Html => object(Cake\View\Helper\HtmlHelper) {}
Form => object(Cake\View\Helper\FormHelper) {}
Expand Down
47 changes: 47 additions & 0 deletions tests/TestCase/View/CellTest.php
Expand Up @@ -14,6 +14,7 @@
*/
namespace Cake\Test\TestCase\View;

use Cake\Cache\Cache;
use Cake\Controller\Controller;
use Cake\Core\Configure;
use Cake\Core\Plugin;
Expand Down Expand Up @@ -247,4 +248,50 @@ public function testCellInheritsCustomViewClass() {
$this->assertSame('TestApp\View\CustomJsonView', $cell->viewClass);
}

/**
* Test cached render.
*
* @return void
*/
public function testCachedRenderSimple() {
$mock = $this->getMock('Cake\Cache\CacheEngine');
$mock->method('init')
->will($this->returnValue(true));
$mock->method('read')
->will($this->returnValue(false));
$mock->expects($this->once())
->method('write')
->with('cell_test_app_view_cell_articles_cell_display', "dummy\n");
Cache::config('default', $mock);

$cell = $this->View->cell('Articles', [], ['cache' => true]);
$result = $cell->render();
$this->assertEquals("dummy\n", $result);
Cache::drop('default');
}

/**
* Test cached render array config
*
* @return void
*/
public function testCachedRenderArrayConfig() {
$mock = $this->getMock('Cake\Cache\CacheEngine');
$mock->method('init')
->will($this->returnValue(true));
$mock->method('read')
->will($this->returnValue(false));
$mock->expects($this->once())
->method('write')
->with('my_key', "dummy\n");
Cache::config('cell', $mock);

$cell = $this->View->cell('Articles', [], [
'cache' => ['key' => 'my_key', 'config' => 'cell']
]);
$result = $cell->render();
$this->assertEquals("dummy\n", $result);
Cache::drop('cell');
}

}
2 changes: 1 addition & 1 deletion tests/TestCase/View/ViewTest.php
Expand Up @@ -861,7 +861,7 @@ public function testElementCache() {
'path' => CACHE . 'views/',
'prefix' => ''
]);
Cache::clear(true, 'test_view');
Cache::clear(false, 'test_view');

$View = $this->PostsController->createView();
$View->elementCache = 'test_view';
Expand Down

0 comments on commit 1c4aa96

Please sign in to comment.