From e24c37c3199f0bb727d326449edc83e1ed896ee9 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Tue, 28 May 2013 22:45:22 -0400 Subject: [PATCH] Split TemplateLoader's cache business into another interface and class. This creates the CacheBackend interface, which will allow for a database cache, and one concrete implementation FileCacheBackend. This implementation implements the existing TemplateLoader cache behavior. --- CHANGES | 11 +- .../tests/views/file_cache_backend_test.php | 82 ++++++++++++ testing/tests/views/template_loader_test.php | 123 +++++------------- views/cache_backend.php | 47 +++++++ views/file_cache_backend.php | 70 ++++++++++ views/template_loader.php | 76 +++++------ 6 files changed, 267 insertions(+), 142 deletions(-) create mode 100644 testing/tests/views/file_cache_backend_test.php create mode 100644 views/cache_backend.php create mode 100644 views/file_cache_backend.php diff --git a/CHANGES b/CHANGES index c88a804..1322407 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,11 @@ -Version 1.1 +Version 2.0 ================================================================================ -- Add hoplite\data\ProfilingPDO to add profiling to database access. -- Add hoplite\base\Profiling static utility class. -- Add hoplite\http\Input for request data sanitization. +- Split caching logic out of views\TemplateLoader into an interface + views\CacheBackend and views\FileCacheBackend. +- Add template usage profiling to views\. +- Add data\ProfilingPDO to add profiling to database access. +- Add base\Profiling static utility class. +- Add http\Input for request data sanitization. Version 1.0 ================================================================================ diff --git a/testing/tests/views/file_cache_backend_test.php b/testing/tests/views/file_cache_backend_test.php new file mode 100644 index 0000000..bb01f06 --- /dev/null +++ b/testing/tests/views/file_cache_backend_test.php @@ -0,0 +1,82 @@ +. + +namespace hoplite\test; +use hoplite\views as views; + +require_once HOPLITE_ROOT . '/views/file_cache_backend.php'; + +class TestFileCacheBackend extends views\FileCacheBackend +{ + public function T_CachePath($path) + { + return $this->_CachePath($path); + } +} + +class FileCacheBackendTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + $path = dirname(__FILE__) . '/cache/'; + $this->fixture = new TestFileCacheBackend($path); + + $files = scandir($path); + foreach ($files as $file) + if ($file[0] != '.') + unlink($path . $file); + } + + public function testCacheCompiled() + { + $files = scandir($this->fixture->cache_path()); + $this->assertEquals(2, count($files)); // Only dotfiles. + + $this->assertNull($this->fixture->GetTemplateDataForName('cache_test', time())); + + $string = 'This is a test.'; + + $this->fixture->StoreCompiledTemplate('cache_test', time(), $string); + $files = scandir($this->fixture->cache_path()); + $this->assertEquals(3, count($files)); + + $actual = file_get_contents($this->fixture->cache_path() . '/cache_test.phpi'); + $this->assertEquals($string, $actual); + } + + public function testCacheHit() + { + $path = $this->fixture->cache_path() . '/cache_test.phpi'; + $expected = 'Cache hit!'; + file_put_contents($path, $expected); + + $files = scandir($this->fixture->cache_path()); + $this->assertEquals(3, count($files)); + + $actual = $this->fixture->GetTemplateDataForName('cache_test', filemtime($path)); + $this->assertEquals($expected, $actual); + } + + public function testCacheMiss() + { + file_put_contents($this->fixture->cache_path() . '/cache_test.phpi', 'Invalid template data'); + $files = scandir($this->fixture->cache_path()); + $this->assertEquals(3, count($files)); + + $actual = $this->fixture->GetTemplateDataForName('cache_test', time() + 60); + $this->assertNull($actual); + } +} diff --git a/testing/tests/views/template_loader_test.php b/testing/tests/views/template_loader_test.php index a784493..8dfc66d 100644 --- a/testing/tests/views/template_loader_test.php +++ b/testing/tests/views/template_loader_test.php @@ -1,11 +1,11 @@ _CachePath($path); - } - - public function T_LoadIfCached($name) - { - return $this->_LoadIfCached($name); - } -} - class TemplateLoaderTest extends \PHPUnit_Framework_TestCase { public function setUp() { - $this->fixture = new TestTemplateLoader(); - - $path = dirname(__FILE__) . '/cache/'; - $files = scandir($path); - foreach ($files as $file) - if ($file[0] != '.') - unlink($path . $file); - } - - protected function _SetUpPaths() - { + $this->fixture = new views\TemplateLoader(); $this->fixture->set_template_path(dirname(__FILE__) . '/%s.tpl'); - $this->fixture->set_cache_path(dirname(__FILE__) . '/cache/'); + + $this->cache = $this->getMock('\\hoplite\\views\\CacheBackend'); + $this->fixture->set_cache_backend($this->cache); } public function testTemplatePath() { - $this->assertEquals('%s.tpl', $this->fixture->template_path()); - $path = '/webapp/views/%s.tpl'; $this->fixture->set_template_path($path); $this->assertEquals($path, $this->fixture->template_path()); } - public function testSetCachePath() - { - $this->assertEquals('/tmp/phalanx_views', $this->fixture->cache_path()); - - $path = '/cache/path'; - $this->fixture->set_cache_path($path); - $this->assertEquals($path, $this->fixture->cache_path()); - } - - public function testCachePath() - { - $this->fixture->set_cache_path('/test/value/'); - $this->assertEquals('/test/value/name.phpi', $this->fixture->T_CachePath('name')); - } - public function testCacheMiss() { - $this->_SetUpPaths(); - - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(2, count($files)); // Only dotfiles. + $this->cache->expects($this->once()) + ->method('GetTemplateDataForName') + ->with($this->equalTo('cache_test')) + ->will($this->returnValue(NULL)); - $this->fixture->Load('cache_test'); + $expected = file_get_contents(sprintf($this->fixture->template_path(), 'cache_test')); - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(3, count($files)); + $this->cache->expects($this->once()) + ->method('StoreCompiledTemplate') + ->with($this->equalTo('cache_test'), + $this->greaterThan(0), + $this->equalTo($expected)); - $expected = file_get_contents(sprintf($this->fixture->template_path(), 'cache_test')); - $actual = file_get_contents($this->fixture->cache_path() . '/cache_test.phpi'); - $this->assertEquals($expected, $actual); + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); } public function testCacheHit() { - $this->_SetUpPaths(); - $expected = 'Cache hit!'; - file_put_contents($this->fixture->cache_path() . '/cache_test.phpi', $expected); - - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(3, count($files)); - - $this->fixture->Load('cache_test'); - - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(3, count($files)); - - $actual = file_get_contents($this->fixture->cache_path() . '/cache_test.phpi'); - $this->assertEquals($expected, $actual); - } - - public function testCacheInvalidate() - { - $this->_SetUpPaths(); - file_put_contents($this->fixture->cache_path() . '/cache_test.phpi', 'Invalid template data'); - - // Need to wait for the mtime to make a difference. - sleep(1); - clearstatcache(); - - // Touch the template to update its mtime. - touch(sprintf($this->fixture->template_path(), 'cache_test')); - - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(3, count($files)); - - $this->fixture->Load('cache_test'); - - $files = scandir($this->fixture->cache_path()); - $this->assertEquals(3, count($files)); - - $expected = file_get_contents(sprintf($this->fixture->template_path(), 'cache_test')); - $actual = file_get_contents($this->fixture->cache_path() . '/cache_test.phpi'); - $this->assertEquals($expected, $actual); + $this->cache->expects($this->once()) + ->method('GetTemplateDataForName') + ->with($this->equalTo('cache_test')) + ->will($this->returnValue($expected)); + + // The cache backend is only consulted once. + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); + $this->assertEquals($expected, $this->fixture->Load('cache_test')->template()); } public function testSingleton() @@ -144,7 +80,6 @@ class TemplateLoaderTest extends \PHPUnit_Framework_TestCase views\TemplateLoader::SetInstance($this->fixture); $this->assertSame(views\TemplateLoader::GetInstance(), $this->fixture); - $this->_SetUpPaths(); $template = views\TemplateLoader::Fetch('cache_test'); $this->assertEquals('This file should be cached.', $template->template()); } diff --git a/views/cache_backend.php b/views/cache_backend.php new file mode 100644 index 0000000..c6fe4a8 --- /dev/null +++ b/views/cache_backend.php @@ -0,0 +1,47 @@ +. + +namespace hoplite\views; + +/*! + CacheBackend is used by the TemplateLoader to store and retrieve compiled + templates. +*/ +interface CacheBackend +{ + /*! + Gets the compiled template data for the template of the given name. + + If the template modification time is newer than what is stored in the cache, + this function should discard the item and return NULL. + + @param string The name of the template. + @param int The UNIX timestamp the uncompiled template was last modified. + + @return string|NULL The cached template data, or NULL if it is not cached. + */ + public function GetTemplateDataForName($name, $modification_time); + + /*! + Stores the compiled template data into the cache with the given name and + template modification time. + + @param string The name of the template. + @param string The compiled template data. + @param int The UNIX timestamp the uncompiled template was last modified. + */ + public function StoreCompiledTemplate($name, $modification_time, $data); +} diff --git a/views/file_cache_backend.php b/views/file_cache_backend.php new file mode 100644 index 0000000..0a47710 --- /dev/null +++ b/views/file_cache_backend.php @@ -0,0 +1,70 @@ +. + +namespace hoplite\views; + +require_once HOPLITE_ROOT . '/views/cache_backend.php'; + +/*! + An instance of a CacheBackend that writes cached representations to the file + system in temporary files. +*/ +class FileCacheBackend implements CacheBackend +{ + /*! @var string The cache path for templates. This should be a path, to which + the cached template name will be appended. This should not + end with a trailing slash. + */ + protected $cache_path = '/tmp/phalanx_views'; + + public function cache_path() { return $this->cache_path; } + + public function __construct($cache_path) + { + $this->cache_path = $cache_path; + } + + public function GetTemplateDataForName($name, $tpl_mtime) + { + $cache_path = $this->_CachePath($name); + + // Make sure the cached file exists and hasn't gotten out-of-date. + if (!file_exists($cache_path) || filemtime($cache_path) < $tpl_mtime) + return NULL; + + // Load the contents of the cache. + $data = @file_get_contents($cache_path); + if ($data === FALSE) + return NULL; + + return $data; + } + + public function StoreCompiledTemplate($name, $mtime, $data) + { + $cache_path = $this->_CachePath($name); + + // Cache the file. + if (file_put_contents($cache_path, $data) === FALSE) + throw new TemplateLoaderException('Could not cache ' . $name . ' to ' . $cache_path); + } + + /*! Returns the cache path for a given template name. */ + protected function _CachePath($name) + { + return $this->cache_path . $name . '.phpi'; + } +} diff --git a/views/template_loader.php b/views/template_loader.php index f0e624d..efa776a 100644 --- a/views/template_loader.php +++ b/views/template_loader.php @@ -1,11 +1,11 @@ template_path = $path; } - function template_path() { return $this->template_path; } - - public function set_cache_path($path) { $this->cache_path = $path; } - public function cache_path() { return $this->cache_path; } + public function template_path() { return $this->template_path; } + + public function set_cache_backend(CacheBackend $backend) { $this->cache_backend = $backend; } + public function cache_backend() { return $this->cache_backend; } /*! Loads a template from a file, creates a Template object, and returns a copy @@ -90,15 +88,15 @@ class TemplateLoader if (isset($this->cache[$name])) return clone $this->cache[$name]; - // Then check the filesystem cache. - $template = $this->_LoadIfCached($name); + // Then check if the cache backend has it. + $template = $this->_QueryCache($name); if ($template) { $this->cache[$name] = $template; return clone $template; } // Finally, parse and cache the template. - $template = $this->_Cache($name); + $template = $this->_Load($name); $this->cache[$name] = $template; return clone $template; } @@ -119,41 +117,36 @@ class TemplateLoader } /*! - Loads a cached filesystem template if it is up-to-date. + Queries the optional CacheBackend for a template. @param string Template name @return Template|NULL */ - protected function _LoadIfCached($name) + protected function _QueryCache($name) { - $cache_path = $this->_CachePath($name); - $tpl_path = $this->_TemplatePath($name); - - // Make sure the cached file exists and hasn't gotten out-of-date. - if (!file_exists($cache_path) || filemtime($cache_path) < filemtime($tpl_path)) + if (!$this->cache_backend) return NULL; - // Load the contents of the cache. - $data = @file_get_contents($cache_path); - if ($data === FALSE) + $tpl_path = $this->_TemplatePath($name); + $data = $this->cache_backend->GetTemplateDataForName($name, filemtime($tpl_path)); + if (!$data) return NULL; return Template::NewWithCompiledData($name, $data); } /*! - Loads a raw template from the file system, stores the compiled template in - the file system, and returns a new template object with that data. + Loads a raw template from the file system and compiles it. If the optional + CacheBackend is present, it will cache the compiled data. @param string Template name. @return Template */ - protected function _Cache($name) + protected function _Load($name) { - $cache_path = $this->_CachePath($name); - $tpl_path = $this->_TemplatePath($name); + $tpl_path = $this->_TemplatePath($name); $data = @file_get_contents($tpl_path); if ($data === FALSE) @@ -161,9 +154,10 @@ class TemplateLoader $template = Template::NewWithData($name, $data); - // Cache the file. - if (file_put_contents($cache_path, $this->cache_prefix . $template->template()) === FALSE) - throw new TemplateLoaderException('Could not cache ' . $name . ' to ' . $cache_path); + if ($this->cache_backend) { + $this->cache_backend->StoreCompiledTemplate( + $name, filemtime($tpl_path), $template->template()); + } return $template; } @@ -173,12 +167,6 @@ class TemplateLoader { return sprintf($this->template_path, $name); } - - /*! Returns the cache path for a given template name. */ - protected function _CachePath($name) - { - return $this->cache_path . $name . '.phpi'; - } } class TemplateLoaderException extends \Exception {} -- 2.22.5