-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
================================================================================
--- /dev/null
+<?php
+// Hoplite
+// Copyright (c) 2013 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+
+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);
+ }
+}
<?php
// Hoplite
// Copyright (c) 2011 Blue Static
-//
+//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or any later version.
-//
+//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
namespace hoplite\test;
use hoplite\views as views;
+require_once HOPLITE_ROOT . '/views/cache_backend.php';
require_once HOPLITE_ROOT . '/views/template_loader.php';
-class TestTemplateLoader extends views\TemplateLoader
-{
- public function T_CachePath($path)
- {
- return $this->_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()
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());
}
--- /dev/null
+<?php
+// Hoplite
+// Copyright (c) 2013 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+
+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);
+}
--- /dev/null
+<?php
+// Hoplite
+// Copyright (c) 2013 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+
+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';
+ }
+}
<?php
// Hoplite
// Copyright (c) 2011 Blue Static
-//
+//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or any later version.
-//
+//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
require_once HOPLITE_ROOT . '/views/template.php';
/*!
- This class knows how to load and cache templates to the file system.
+ The TemplateLoader manages the reading of templates from the file system.
+
+ It can also work with a CacheBackend to store the compiled template data to
+ increase performance.
+
+ Internally it also maintains a cache that it will consult after a template is
+ loaded for the first time.
*/
class TemplateLoader
{
*/
protected $template_path = '%s.tpl';
- /*! @var string The cache path for templates. Unlike |$template_path|, this
- should only 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';
-
- /*! @var string A header to put at the beginning of each cached template file,
- common for setting include paths or cache debug information.
- */
- protected $cache_prefix = '';
+ /*! @var CacheBackend A cache for compiled template data. */
+ protected $cache_backend = NULL;
/*! @var array An array of Template objects, keyed by the template name. */
protected $cache = array();
/*! Accessors */
public function set_template_path($path) { $this->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
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;
}
}
/*!
- 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)
$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;
}
{
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 {}