Permalink
Browse files

SUB-VIEWS pragma implementation

- Added ability to render "sub views", implementing a two-step view pattern.
  • Loading branch information...
1 parent b847686 commit 857274f44bad983185100b4d83e3bcb3afa24982 @weierophinney weierophinney committed Sep 14, 2010
View
98 .project
@@ -1,76 +1,22 @@
-mustache=/home/matthew/git/phly_mustache CD=. filter="*.php *.html *.xml *.txt" {
- LICENSE
- README.txt
- TODO
- library=library {
- Phly=Phly {
- Mustache=Mustache {
- _autoload.php
- Exception.php
- Lexer.php
- Mustache.php
- Pragma.php
- Renderer.php
- Exception=Exception {
- InvalidDelimiterException.php
- InvalidEscaperException.php
- InvalidPartialsException.php
- InvalidPragmaNameException.php
- InvalidStateException.php
- InvalidTemplateException.php
- InvalidTemplatePathException.php
- InvalidVariableNameException.php
- TemplateNotFoundException.php
- UnbalancedSectionException.php
- UnbalancedTagException.php
- UnregisteredPragmaException.php
- }
- Pragma=Pragma {
- AbstractPragma.php
- ImplicitIterator.php
- }
- }
- }
- }
- tests=tests {
- bootstrap.php
- phpunit.xml
- PhlyTest=PhlyTest {
- Mustache=Mustache {
- MustacheTest.php
- TestAsset=TestAsset {
- NestedObject.php
- PartialView.php
- ViewWithArrayEnumerable.php
- ViewWithHigherOrderSection.php
- ViewWithMethod.php
- ViewWithNestedObjects.php
- ViewWithObjectForPartial.php
- ViewWithTraversableObject.php
- }
- templates=templates filter="*.mustache" {
- crazy_recursive.mustache
- node.mustache
- partial-template.mustache
- partial-with-section.mustache
- renders-file-templates.mustache
- template-with-aliased-partial.mustache
- template-with-comments.mustache
- template-with-conditional.mustache
- template-with-delim-set-in-section.mustache
- template-with-delim-set.mustache
- template-with-dereferencing.mustache
- template-with-enumerable.mustache
- template-with-implicit-iterator.mustache
- template-with-inverted-section.mustache
- template-with-method-substitution.mustache
- template-with-partial.mustache
- template-with-partials-and-delim-set.mustache
- template-with-pragma-in-section.mustache
- template-with-pragma-and-partial.mustache
- template-with-sections-and-delim-set.mustache
- }
- }
- }
- }
-}
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>phly_mustache</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.dltk.core.scriptbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.php.core.PHPNature</nature>
+ </natures>
+</projectDescription>
View
9 .vimproject
@@ -28,6 +28,8 @@ mustache=/home/matthew/git/phly_mustache CD=. filter="*.php *.html *.xml *.txt"
Pragma=Pragma {
AbstractPragma.php
ImplicitIterator.php
+ SubView.php
+ SubViews.php
}
}
}
@@ -38,6 +40,9 @@ mustache=/home/matthew/git/phly_mustache CD=. filter="*.php *.html *.xml *.txt"
PhlyTest=PhlyTest {
Mustache=Mustache {
MustacheTest.php
+ Pragma=Pragma {
+ SubViewsTest.php
+ }
TestAsset=TestAsset {
NestedObject.php
PartialView.php
@@ -54,6 +59,9 @@ mustache=/home/matthew/git/phly_mustache CD=. filter="*.php *.html *.xml *.txt"
partial-template.mustache
partial-with-section.mustache
renders-file-templates.mustache
+ sub-view-containing-sub-views.mustache
+ sub-view-sidebar.mustache
+ sub-view-template.mustache
template-with-aliased-partial.mustache
template-with-comments.mustache
template-with-conditional.mustache
@@ -69,6 +77,7 @@ mustache=/home/matthew/git/phly_mustache CD=. filter="*.php *.html *.xml *.txt"
template-with-pragma-in-section.mustache
template-with-pragma-and-partial.mustache
template-with-sections-and-delim-set.mustache
+ template-with-sub-view.mustache
}
}
}
View
132 README.txt
@@ -172,6 +172,43 @@ A few things to remember when using partials:
Basically, partials render in their own scope. If you remember that one rule,
you should have no problems.
+Whitespace Stripping
+====================
+Because this is a very literal compiler, whitespace can sometimes be an issue. A
+number of measures have been built in to reduce such issues by stripping
+whitespace (primarily newlines) surrounding certain tokens, but they come at a
+slight performance penalty.
+
+For markup languages like XML, XHTML or HTML5, you likely will not run into
+issues in the final rendered output. As such, you can optionally disable
+whitespace stripping:
+
+ $mustache->getLexer()->disableStripWhitespace(true);
+
+Caching Tokens
+==============
+Tokens from parsed templates may be cached for later usage; alternately, a new
+instance of phly_mustache may be seeded with cached tokens from a previous
+instance.
+
+To get the list of tokens, use the following:
+
+ $tokens = $mustache->getAllTokens();
+
+This will return a list of template name/token list pairs, based on the
+templates compiled by this instance. You may then seed another instance using
+the following:
+
+ $mustache->restoreTokens($tokens);
+
+This will overwrite any tokens already compiled by that instance.
+
+Since the tokens are template name/token list pairs, you can safely pass them to
+array_merge(), allowing multiple instances of phly_mustache to build up a large
+cache of template tokens. This will greatly improve performance when rendering
+templates on subsequent calls -- particularly if you cache the tokens in a
+memory store such as memcached.
+
Pragmas
=======
Pragmas are tags of the form:
@@ -205,52 +242,77 @@ scope, which means:
empty set of pragmas, and must declare any pragmas it requires for
appropriate rendering.
-An example is the "IMPLICIT-ITERATOR" pragma, which is included with this
-distribution. This pragma allows iteration of indexed arrays or Traversable
-objects with scalar values, with the option of specifying the iterator "key" to
-use within the template. You can review
+For ideas on how you might use or implement pragmas, examine the pragmas shipped
+with phly_mustache.
- library/Phly/Mustache/Pragma/ImplicitIterator.php
+Pragmas shipped with phly_mustache
+----------------------------------
-for details on how it accomplishes this, as well as the unit test
+IMPLICIT-ITERATOR
+This pragma allows iteration of indexed arrays or Traversable objects with
+scalar values, with the option of specifying the iterator "key" to use within
+the template. By default, a variable key "." will be replaced by the current
+value of the iterator.
- PhlyTest\Mustache\MustacheTest::testHonorsImplicitIteratorPragma()
+A sample template:
-for details on usage.
+ {{#some_iterable_data}}
+ {{.}}
+ {{/some_iterable_data}}
-Whitespace Stripping
-====================
-Because this is a very literal compiler, whitespace can sometimes be an issue. A
-number of measures have been built in to reduce such issues by stripping
-whitespace (primarily newlines) surrounding certain tokens, but they come at a
-slight performance penalty.
+To use an explicit iterator key, specify it via the "iterator" option of the
+pragma:
-For markup languages like XML, XHTML or HTML5, you likely will not run into
-issues in the final rendered output. As such, you can optionally disable
-whitespace stripping:
+ {{%IMPLICIT-ITERATOR iterator=bob}}
+ {{#some_iterable_data}}
+ {{bob}}
+ {{/some_iterable_data}}
- $mustache->getLexer()->disableStripWhitespace(true);
+SUB-VIEWS
+The Sub-Views pragma allows you to implement the two-step view pattern using
+Mustache. When active, any variable whose value is an instance of
+Phly\Mustache\Pragma\SubView will be substituted by rendering the template and
+view that object encapsulates.
-Caching Tokens
-==============
-Tokens from parsed templates may be cached for later usage; alternately, a new
-instance of phly_mustache may be seeded with cached tokens from a previous
-instance.
+The SubView class takes a template name and a view as a constructor:
-To get the list of tokens, use the following:
+ use Phly\Mustache\Pragma\SubView;
+ $subView = new SubView('some-partial', array('name' => 'Matthew'));
- $tokens = $mustache->getAllTokens();
+That object is then assigned as a value to a view key:
-This will return a list of template name/token list pairs, based on the
-templates compiled by this instance. You may then seed another instance using
-the following:
+ $view = new \stdClass;
+ $view->content = $subView;
- $mustache->restoreTokens($tokens);
+The template might look like this:
-This will overwrite any tokens already compiled by that instance.
+ {{!layout}}
+ {{%SUB-VIEWS}}
+ <html>
+ <body>
+ {{content}}
+ </body>
+ </html>
-Since the tokens are template name/token list pairs, you can safely pass them to
-array_merge(), allowing multiple instances of phly_mustache to build up a large
-cache of template tokens. This will greatly improve performance when rendering
-templates on subsequent calls -- particularly if you cache the tokens in a
-memory store such as memcached.
+and the partial like this:
+
+ {{!some-partial}}
+ Hello, {{name}}!
+
+Rendering the view:
+
+ use Phly\Mustache\Mustache,
+ Phly\Mustache\Pragma\SubViews;
+ $mustache = new Mustache();
+ $subViews = new SubViews($mustache);
+ $rendered = $mustache->render('layout', $view);
+
+will result in:
+
+ <html>
+ <body>
+ Hello, Matthew!
+ </body>
+ </html>
+
+Sub views may be nested, and re-used.
View
75 library/Phly/Mustache/Pragma/SubView.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * phly_mustache
+ *
+ * @category Phly
+ * @package phly_mustache
+ * @subpackage Pragma
+ * @copyright Copyright (c) 2010 Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ */
+
+/** @namespace */
+namespace Phly\Mustache\Pragma;
+
+use Phly\Mustache\Exception\InvalidTemplateException,
+ InvalidArgumentException;
+
+/**
+ * Sub-view description
+ *
+ * Objects extending this may be used to describe sub-views that should be
+ * rendered as substitutions for template variables.
+ *
+ * @category Phly
+ * @package phly_mustache
+ * @subpackage Pragma
+ */
+class SubView
+{
+ /** @var string */
+ protected $template;
+
+ /** @var null|array|object */
+ protected $view;
+
+ /**
+ * Constructor
+ *
+ * @param string $template
+ * @param null|array|object $view
+ * @return void
+ */
+ public function __construct($template, $view = null)
+ {
+ if (!is_string($template)) {
+ throw new InvalidTemplateException();
+ }
+ if (null !== $view && !is_array($view) && !is_object($view)) {
+ throw new InvalidArgumentException('View must be an array or object');
+ }
+ $this->template = $template;
+ $this->view = $view;
+ }
+
+ /**
+ * Retrieve template
+ *
+ * @return string
+ */
+ public function getTemplate()
+ {
+ return $this->template;
+ }
+
+ /**
+ * Retrieve view
+ *
+ * @return array|object
+ */
+ public function getView()
+ {
+ return $this->view;
+ }
+}
+
View
171 library/Phly/Mustache/Pragma/SubViews.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * phly_mustache
+ *
+ * @category Phly
+ * @package phly_mustache
+ * @subpackage Pragma
+ * @copyright Copyright (c) 2010 Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ */
+
+/** @namespace */
+namespace Phly\Mustache\Pragma;
+
+use Phly\Mustache\Mustache,
+ Phly\Mustache\Lexer;
+
+/**
+ * SUB-VIEWS pragma
+ *
+ * When enabled, allows passing "sub-views". A sub view is an object
+ * implementing SubView, which contains the following methods:
+ * - getTemplate()
+ * - getView()
+ * When detected as the value of a variable, the pragma will render the given
+ * template using the view provided, and return that value as the value of the
+ * variable.
+ *
+ * Consider the following template:
+ * <code>
+ * {{%SUB-VIEWS}}
+ * <html>
+ * <head>
+ * {{>header}}
+ * </head>
+ * <body>
+ * {{content}}
+ * </body>
+ * </html>
+ * </code>
+ *
+ * And the following partials:
+ * <code>
+ * {{!header}}
+ * <title>{{title}}</title>
+ *
+ * {{!controller/action}}
+ * {{greeting}}, {{name}}!
+ * </code>
+ *
+ *
+ * Along with the following view:
+ * <code>
+ * $content = new SubView('controller/action', array(
+ * 'name' => 'Matthew',
+ * 'greeting' => 'Welcome',
+ * ));
+ * $view = array(
+ * 'title' => 'Greeting Page',
+ * 'content' => $content,
+ * );
+ * </code>
+ *
+ * Rendered, this would now be:
+ * <code>
+ * <html>
+ * <head>
+ * <title>Greeting Page</title>
+ * </head>
+ * <body>
+ * Welcome, Matthew!
+ * </body>
+ * </html>
+ * </code>
+ *
+ * @category Phly
+ * @package phly_mustache
+ * @subpackage Pragma
+ */
+class SubViews extends AbstractPragma
+{
+ protected $name = 'SUB-VIEWS';
+
+ protected $tokensHandled = array(
+ Lexer::TOKEN_VARIABLE,
+ );
+
+ /** @var Mustache */
+ protected $manager;
+
+ /**
+ * Constructor
+ *
+ * @param Mustache $manager
+ * @return void
+ */
+ public function __construct(Mustache $manager = null)
+ {
+ if (null !== $manager) {
+ $this->setManager($manager);
+ }
+ }
+
+ /**
+ * Set manager object
+ *
+ * Sets manager object and registers self as a pragma on the renderer.
+ *
+ * @param Mustache $manager
+ * @return SubViews
+ */
+ public function setManager(Mustache $manager)
+ {
+ $this->manager = $manager;
+ $this->manager->getRenderer()->addPragma($this);
+ return $this;
+ }
+
+ /**
+ * Retrieve manager object
+ *
+ * @return Mustache
+ */
+ public function getManager()
+ {
+ return $this->manager;
+ }
+
+ /**
+ * Handle a given token
+ *
+ * Returning an empty value returns control to the renderer.
+ *
+ * @param int $token
+ * @param mixed $data
+ * @param mixed $view
+ * @param array $options
+ * @return mixed
+ */
+ public function handle($token, $data, $view, array $options)
+ {
+ // If the view doesn't have a value for this item, nothing to do
+ if (!isset($view[$data])) {
+ return;
+ }
+
+ // If the view value is not a SubView, we can't handle it here
+ if (!$view[$data] instanceof SubView) {
+ return;
+ }
+
+ // If we don't have a manager instance, can't do anything with it
+ if (null === ($manager = $this->getManager())) {
+ return;
+ }
+
+ $subView = $view[$data];
+
+ // Get template
+ $template = $subView->getTemplate();
+
+ // Get sub view; use current view if none found
+ $localView = $subView->getView();
+ if (null === $localView) {
+ $localView = $view;
+ }
+
+ // Render sub view and return it
+ return $manager->render($template, $localView);
+ }
+}
View
4 library/Phly/Mustache/_autoload.php
@@ -19,10 +19,12 @@
'Phly\\Mustache\\Exception' => __DIR__ . DIRECTORY_SEPARATOR . 'Exception.php',
'Phly\\Mustache\\Pragma\\AbstractPragma' => __DIR__ . DIRECTORY_SEPARATOR . 'Pragma/AbstractPragma.php',
'Phly\\Mustache\\Pragma\\ImplicitIterator' => __DIR__ . DIRECTORY_SEPARATOR . 'Pragma/ImplicitIterator.php',
+ 'Phly\\Mustache\\Pragma\\SubView' => __DIR__ . DIRECTORY_SEPARATOR . 'Pragma/SubView.php',
+ 'Phly\\Mustache\\Pragma\\SubViews' => __DIR__ . DIRECTORY_SEPARATOR . 'Pragma/SubViews.php',
'Phly\\Mustache\\Pragma' => __DIR__ . DIRECTORY_SEPARATOR . 'Pragma.php',
);
spl_autoload_register(function($class) use ($_map) {
if (array_key_exists($class, $_map)) {
require_once $_map[$class];
}
-});
+});
View
77 tests/PhlyTest/Mustache/Pragma/SubViewsTest.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * phly_mustache
+ *
+ * @category PhlyTest
+ * @package phly_mustache
+ * @subpackage UnitTests
+ * @copyright Copyright (c) 2010 Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ */
+
+/** @namespace */
+namespace PhlyTest\Mustache\Pragma;
+
+use Phly\Mustache\Mustache,
+ Phly\Mustache\Pragma\SubViews,
+ Phly\Mustache\Pragma\SubView;
+
+/**
+ * Unit tests for Sub-Views pragma
+ *
+ * @category Phly
+ * @package phly_mustache
+ * @subpackage UnitTests
+ */
+class SubViewsTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->mustache = new Mustache();
+ $this->mustache->setTemplatePath(__DIR__ . '/../templates');
+ $subViews = new SubViews();
+ $subViews->setManager($this->mustache);
+ $this->mustache->getRenderer()->addPragma($subViews);
+ }
+
+ public function testSubViewContentIsCapturedInParent()
+ {
+ $content = new SubView('sub-view-template', array(
+ 'greeting' => 'Hello',
+ 'name' => 'World',
+ ));
+ $view = array('content' => $content);
+ $test = $this->mustache->render('template-with-sub-view', $view);
+ $this->assertRegexp('/Header content.*?Hello, World.*Footer content/s', $test);
+ }
+
+ public function testRendersNestedSubViews()
+ {
+ $sidebar = new SubView('sub-view-sidebar', array('name' => 'final'));
+ $content = new SubView('sub-view-template', array(
+ 'greeting' => 'Goodbye',
+ 'name' => 'cruel world',
+ ));
+ $mainContent = new SubView('sub-view-containing-sub-views', array(
+ 'content' => $content,
+ 'sidebar' => $sidebar,
+ ));
+ $view = array('content' => $mainContent);
+ $test = $this->mustache->render('template-with-sub-view', $view);
+ $this->assertRegexp('/Header content.*?Goodbye, cruel world.*?break.*?final sidebar.*?Footer content/s', $test);
+ }
+
+ public function testSubViewUsesParentViewWhenNoViewProvided()
+ {
+ $sidebar = new SubView('sub-view-sidebar');
+ $content = new SubView('sub-view-template');
+ $view = array(
+ 'name' => 'bat',
+ 'greeting' => 'Shabaz',
+ 'content' => $content,
+ 'sidebar' => $sidebar,
+ );
+ $test = $this->mustache->render('sub-view-containing-sub-views', $view);
+ $this->assertRegexp('/Shabaz, bat.*?bat sidebar/s', $test, $test);
+ }
+}
View
4 tests/PhlyTest/Mustache/templates/sub-view-containing-sub-views.mustache
@@ -0,0 +1,4 @@
+{{%SUB-VIEWS}}
+{{content}}
+break
+{{sidebar}}
View
1 tests/PhlyTest/Mustache/templates/sub-view-sidebar.mustache
@@ -0,0 +1 @@
+This is the {{name}} sidebar
View
1 tests/PhlyTest/Mustache/templates/sub-view-template.mustache
@@ -0,0 +1 @@
+{{greeting}}, {{name}}!
View
5 tests/PhlyTest/Mustache/templates/template-with-sub-view.mustache
@@ -0,0 +1,5 @@
+{{%SUB-VIEWS}}
+Header content
+ {{content}}
+Footer content
+

0 comments on commit 857274f

Please sign in to comment.