Add WIP TemplateLoader
authorRobert Sesek <rsesek@bluestatic.org>
Wed, 27 Jul 2011 13:51:25 +0000 (09:51 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Wed, 27 Jul 2011 13:51:25 +0000 (09:51 -0400)
testing/tests/views/template_loader_test.php [new file with mode: 0644]
views/template_loader.php [new file with mode: 0644]

diff --git a/testing/tests/views/template_loader_test.php b/testing/tests/views/template_loader_test.php
new file mode 100644 (file)
index 0000000..0844696
--- /dev/null
@@ -0,0 +1,192 @@
+<?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
+// 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\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 = '<strong>Some day, is it not, <'.'?php echo "Rob" ?'.'>?</strong>';
+    $this->assertEquals($data, $view->T_ProcessTemplate($data));
+  }
+
+  public function testProcessTemplateMacro()
+  {
+    $view = new TestView('test');
+    $in   = 'foo $[some.value] bar';
+    $out  = 'foo <?php echo $view->GetHTML("some.value") ?> bar';
+    $this->assertEquals($out, $view->T_ProcessTemplate($in));
+  }
+
+  public function testProcessTemplateShortTags()
+  {
+    $view = new TestView('test');
+    $in   = 'foo <?php echo "Not this one"; ?> bar <? echo "But this one!" ?> moo';
+    $out  = 'foo <?php echo "Not this one"; ?> bar <?php echo "But this one!" ?> 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 (file)
index 0000000..043ff71
--- /dev/null
@@ -0,0 +1,152 @@
+<?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
+// 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;
+
+/*!
+  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 {}