From 33c16b3697751a23c439d344b03089a96ce582e6 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Wed, 27 Jul 2011 09:51:25 -0400 Subject: [PATCH] Add WIP TemplateLoader --- testing/tests/views/template_loader_test.php | 192 +++++++++++++++++++ views/template_loader.php | 152 +++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 testing/tests/views/template_loader_test.php create mode 100644 views/template_loader.php diff --git a/testing/tests/views/template_loader_test.php b/testing/tests/views/template_loader_test.php new file mode 100644 index 0000000..0844696 --- /dev/null +++ b/testing/tests/views/template_loader_test.php @@ -0,0 +1,192 @@ +. +/* +namespace hoplite\test; +use hoplite\views\Template; + +require_once TEST_ROOT . '/tests/views.php'; + +class ViewTest extends \PHPUnit_Framework_TestCase +{ + public $saved_singleton = array(); + + public function setUp() + { + $this->saved_singleton['template_path'] = View::template_path(); + $this->saved_singleton['cache_path'] = View::cache_path(); + + $path = dirname(__FILE__) . '/data/cache/'; + $files = scandir($path); + foreach ($files as $file) + if ($file[0] != '.') + unlink($path . $file); + } + + public function tearDown() + { + View::set_template_path($this->saved_singleton['template_path']); + View::set_cache_path($this->saved_singleton['cache_path']); + } + + public function testTemplatePath() + { + $this->assertEquals('%s.tpl', View::template_path()); + + $path = '/webapp/views/%s.tpl'; + View::set_template_path($path); + $this->assertEquals($path, View::template_path()); + } + + public function testSetCachePath() + { + $this->assertEquals('/tmp/phalanx_views', View::cache_path()); + + $path = '/cache/path'; + View::set_cache_path($path); + $this->assertEquals($path, View::cache_path()); + } + + public function testCtorAndTemplateName() + { + $view = $this->getMock('phalanx\views\View', NULL, array('test_tpl')); + $this->assertEquals('test_tpl', $view->template_name()); + + $this->assertType('phalanx\base\Dictionary', $view->vars()); + $this->assertEquals(0, $view->vars()->Count()); + } + + public function testProcessTemplateEntities() + { + $view = new TestView('test'); + $data = 'Some day, is it not, <'.'?php echo "Rob" ?'.'>?'; + $this->assertEquals($data, $view->T_ProcessTemplate($data)); + } + + public function testProcessTemplateMacro() + { + $view = new TestView('test'); + $in = 'foo $[some.value] bar'; + $out = 'foo GetHTML("some.value") ?> bar'; + $this->assertEquals($out, $view->T_ProcessTemplate($in)); + } + + public function testProcessTemplateShortTags() + { + $view = new TestView('test'); + $in = 'foo bar moo'; + $out = 'foo bar moo'; + $this->assertEquals($out, $view->T_ProcessTemplate($in)); + } + + public function testMagicGetterSetter() + { + $view = new View('test'); + $view->foo = 'abc'; + $this->assertEquals('abc', $view->foo); + $this->assertEquals('abc', $view->vars()->Get('foo')); + + $view->foo = array(); + $view->{"foo.bar"} = '123'; + $this->assertEquals('123', $view->{"foo.bar"}); + $this->assertEquals('123', $view->vars()->Get('foo.bar')); + } + + public function testCachePath() + { + View::set_cache_path('/test/value'); + $view = new TestView('name'); + $this->assertEquals('/test/value/name.phpi', $view->T_CachePath($view->template_name())); + } + + public function testCacheMiss() + { + TestView::SetupPaths(); + $view = new TestView('cache_test'); + + $files = scandir(View::cache_path()); + $this->assertEquals(2, count($files)); // Only dotfiles. + + $view->T_Cache(); + + $files = scandir(View::cache_path()); + $this->assertEquals(3, count($files)); + + $expected = file_get_contents(sprintf(View::template_path(), 'cache_test')); + $actual = file_get_contents(View::cache_path() . '/cache_test.phpi'); + $this->assertEquals($expected, $actual); + } + + public function testCacheHit() + { + $expected = 'Cache hit!'; + TestView::SetupPaths(); + file_put_contents(View::cache_path() . '/cache_test.phpi', $expected); + $view = new TestView('cache_test'); + + $files = scandir(View::cache_path()); + $this->assertEquals(3, count($files)); + + $view->T_Cache(); + + $files = scandir(View::cache_path()); + $this->assertEquals(3, count($files)); + + $actual = file_get_contents(View::cache_path() . '/cache_test.phpi'); + $this->assertEquals($expected, $actual); + } + + public function testCacheInvalidate() + { + TestView::SetupPaths(); + file_put_contents(View::cache_path() . '/cache_test.phpi', 'Invalid template data'); + $view = new TestView('cache_test'); + + // Need to wait for the mtime to make a difference. + sleep(1); + clearstatcache(); + + // Touch the template to update its mtime. + touch(sprintf(View::template_path(), 'cache_test')); + + $files = scandir(View::cache_path()); + $this->assertEquals(3, count($files)); + + $view->T_Cache(); + + $files = scandir(View::cache_path()); + $this->assertEquals(3, count($files)); + + $expected = file_get_contents(sprintf(View::template_path(), 'cache_test')); + $actual = file_get_contents(View::cache_path() . '/cache_test.phpi'); + $this->assertEquals($expected, $actual); + } + + public function testRender() + { + TestView::SetupPaths(); + + $view = new TestView('render_test'); + $view->name = 'Rob'; + + ob_start(); + $view->Render(); + $actual = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals('Hi, my name is Rob. This is undefined: .', $actual); + } +} +*/ \ No newline at end of file diff --git a/views/template_loader.php b/views/template_loader.php new file mode 100644 index 0000000..043ff71 --- /dev/null +++ b/views/template_loader.php @@ -0,0 +1,152 @@ +. + +namespace hoplite\views; + +/*! + This class knows how to load and cache templates to the file system. +*/ +class TemplateLoader +{ + /*! @var TemplateLoader Singleton instance */ + static private $instance = NULL; + + /*! @var string Base path for loading the template file. Use %s to indicate + where the name (passed to the constructor) should be + substituted. + */ + 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 array An array of Template objects, keyed by the template name. */ + protected $cache = array(); + + /*! Gets the singleton instance. */ + static public function GetInstance() + { + if (!self::$instance) + self::$instance = new __class__(); + return self::$instance(); + } + /*! Sets the singleton instance. */ + static public function SetInstance($instance) { self::$instance = $instance; } + + /*! 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; } + + /*! + Loads a template from a file, creates a Template object, and returns a copy + of that object. + + @param string Template name, with wich the template plath is formatted. + + @return Template Clone of the cached template. + */ + public function Load($name) + { + // First check the memory cache. + if (isset($this->cache[$name])) + return clone $this->cache[$name]; + + // Then check the filesystem cache. + $template = $this->_LoadIfCached($name); + if ($template) { + $this->cache[$name] = $template; + return clone $template; + } + + // Finally, parse and cache the template. + $template = $this->_Cache($name); + $this->cache[$name] = $template; + return clone $template; + } + + /*! + Loads a cached filesystem template if it is up-to-date. + + @param string Template name + + @return Template|NULL + */ + protected function _LoadIfCached($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)) { + return NULL; + + // Load the contents of the cache. + $data = @file_get_contents($cache_path); + if ($data === FALSE) + return NULL; + + return Template::NewWithData($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. + + @param string Template name. + + @return Template + */ + protected function _Cache($name) + { + $cache_path = $this->_CachePath($name); + $tpl_path = $this->_TemplatePath($name); + + $data = @file_get_contents($tpl_path); + if ($data === FALSE) + throw new TemplateLoaderException('Could not load template ' . $this->template_name); + + $template = Template::NewWithCompiledData($data); + + // Cache the file. + if (!file_put_contents($cache_path, $this->cache_prefix . $template->template())) + throw new TemplateLoaderException('Could not cache ' . $this->template_name . ' to ' . $cache_path); + } + + /*! Returns the template path for a given template name. */ + protected function _TemplatePath($name) + { + return sprintf($this->template_path, $name); + } + + /*! Returns the cache path for a given template name. */ + protected function _CachePath($name) + { + return self::$cache_path . '/' . $name . '.phpi'; + } +} + +class TemplateLoaderException extends \Exception {} -- 2.43.5