Merge pull request #82 from bobthecow/feature/helpers

Add a "helpers" implementation to 2.0-dev
This commit is contained in:
Justin Hileman
2012-03-14 17:52:45 -07:00
5 changed files with 487 additions and 1 deletions
+158
View File
@@ -0,0 +1,158 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mustache;
/**
* A collection of helpers for a Mustache instance.
*/
class HelperCollection {
private $helpers = array();
/**
* Helper Collection constructor.
*
* Optionally accepts an array (or \Traversable) of `$name => $helper` pairs.
*
* @throws \InvalidArgumentException if the $helpers argument isn't an array or \Traversable
*
* @param array|Traversable $helpers (default: null)
*/
public function __construct($helpers = null) {
if ($helpers !== null) {
if (!is_array($helpers) && !$helpers instanceof \Traversable) {
throw new \InvalidArgumentException('HelperCollection constructor expects an array of helpers');
}
foreach ($helpers as $name => $helper) {
$this->add($name, $helper);
}
}
}
/**
* Magic mutator.
*
* @see \Mustache\HelperCollection::add
*
* @param string $name
* @param mixed $helper
*/
public function __set($name, $helper) {
$this->add($name, $helper);
}
/**
* Add a helper to this collection.
*
* @param string $name
* @param mixed $helper
*/
public function add($name, $helper) {
$this->helpers[$name] = $helper;
}
/**
* Magic accessor.
*
* @see \Mustache\HelperCollection::get
*
* @param string $name
*
* @return mixed Helper
*/
public function __get($name) {
return $this->get($name);
}
/**
* Get a helper by name.
*
* @param string $name
*
* @return mixed Helper
*/
public function get($name) {
if (!$this->has($name)) {
throw new \InvalidArgumentException('Unknown helper: '.$name);
}
return $this->helpers[$name];
}
/**
* Magic isset().
*
* @see \Mustache\HelperCollection::has
*
* @param string $name
*
* @return boolean True if helper is present
*/
public function __isset($name) {
return $this->has($name);
}
/**
* Check whether a given helper is present in the collection.
*
* @param string $name
*
* @return boolean True if helper is present
*/
public function has($name) {
return array_key_exists($name, $this->helpers);
}
/**
* Magic unset().
*
* @see \Mustache\HelperCollection::remove
*
* @param string $name
*/
public function __unset($name) {
$this->remove($name);
}
/**
* Check whether a given helper is present in the collection.
*
* @throws \InvalidArgumentException if the requested helper is not present.
*
* @param string $name
*/
public function remove($name) {
if (!$this->has($name)) {
throw new \InvalidArgumentException('Unknown helper: '.$name);
}
unset($this->helpers[$name]);
}
/**
* Clear the helper collection.
*
* Removes all helpers from this collection
*/
public function clear() {
$this->helpers = array();
}
/**
* Check whether the helper collection is empty.
*
* @return boolean True if the collection is empty
*/
public function isEmpty() {
return empty($this->helpers);
}
}
+99
View File
@@ -39,6 +39,7 @@ class Mustache {
private $cache = null;
private $loader;
private $partialsLoader;
private $helpers;
private $charset = 'UTF-8';
/**
@@ -63,6 +64,13 @@ class Mustache {
* // efficient or lazy as a Filesystem (or database) loader.
* 'partials' => array('foo' => file_get_contents(__DIR__.'/views/partials/foo.mustache')),
*
* // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
* // sections), or any other valid Mustache context value. They will be prepended to the context stack,
* // so they will be available in any template loaded by this Mustache instance.
* 'helpers' => array('i18n' => function($text) {
* // do something translatey here...
* }),
*
* // character set for `htmlspecialchars`. Defaults to 'UTF-8'
* 'charset' => 'ISO-8859-1',
* );
@@ -90,6 +98,10 @@ class Mustache {
$this->setPartials($options['partials']);
}
if (isset($options['helpers'])) {
$this->setHelpers($options['helpers']);
}
if (isset($options['charset'])) {
$this->charset = $options['charset'];
}
@@ -187,6 +199,93 @@ class Mustache {
$loader->setTemplates($partials);
}
/**
* Set an array of Mustache helpers.
*
* An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
* any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
* any template loaded by this Mustache instance.
*
* @throws \InvalidArgumentException if $helpers is not an array or \Traversable
*
* @param array|Traversable $helpers
*/
public function setHelpers($helpers) {
if (!is_array($helpers) && !$helpers instanceof \Traversable) {
throw new \InvalidArgumentException('setHelpers expects an array of helpers');
}
$this->getHelpers()->clear();
foreach ($helpers as $name => $helper) {
$this->addHelper($name, $helper);
}
}
/**
* Get the current set of Mustache helpers.
*
* @see \Mustache\Mustache::setHelpers
*
* @return \Mustache\HelperCollection
*/
public function getHelpers() {
if (!isset($this->helpers)) {
$this->helpers = new HelperCollection;
}
return $this->helpers;
}
/**
* Add a new Mustache helper.
*
* @see \Mustache\Mustache::setHelpers
*
* @param string $name
* @param mixed $helper
*/
public function addHelper($name, $helper) {
$this->getHelpers()->add($name, $helper);
}
/**
* Get a Mustache helper by name.
*
* @see \Mustache\Mustache::setHelpers
*
* @param string $name
*
* @return mixed Helper
*/
public function getHelper($name) {
return $this->getHelpers()->get($name);
}
/**
* Check whether this Mustache instance has a helper.
*
* @see \Mustache\Mustache::setHelpers
*
* @param string $name
*
* @return boolean True if the helper is present
*/
public function hasHelper($name) {
return $this->getHelpers()->has($name);
}
/**
* Remove a helper by name.
*
* @see \Mustache\Mustache::setHelpers
*
* @param string $name
*/
public function removeHelper($name) {
$this->getHelpers()->remove($name);
}
/**
* Set the Mustache Tokenizer instance.
*
+24 -1
View File
@@ -57,7 +57,7 @@ abstract class Template {
* @return string Rendered template
*/
public function render($context = array()) {
return $this->renderInternal(new Context($context));
return $this->renderInternal($this->prepareContextStack($context));
}
/**
@@ -119,4 +119,27 @@ abstract class Template {
}
}
/**
* Helper method to prepare the Context stack.
*
* Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
*
* @param mixed $context Optional first context frame (default: null)
*
* @return \Mustache\Context
*/
protected function prepareContextStack($context = null) {
$stack = new Context;
$helpers = $this->mustache->getHelpers();
if (!$helpers->isEmpty()) {
$stack->push($helpers);
}
if (!empty($context)) {
$stack->push($context);
}
return $stack;
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mustache\Test;
use Mustache\HelperCollection;
class HelperCollectionTest extends \PHPUnit_Framework_TestCase {
public function testConstructor() {
$foo = function() { echo 'foo'; };
$bar = 'BAR';
$helpers = new HelperCollection(array(
'foo' => $foo,
'bar' => $bar,
));
$this->assertSame($foo, $helpers->get('foo'));
$this->assertSame($bar, $helpers->get('bar'));
}
public function testAccessorsAndMutators() {
$foo = function() { echo 'foo'; };
$bar = 'BAR';
$helpers = new HelperCollection;
$this->assertTrue($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$helpers->add('foo', $foo);
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$helpers->add('bar', $bar);
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$helpers->remove('foo');
$this->assertFalse($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
}
public function testMagicMethods() {
$foo = function() { echo 'foo'; };
$bar = 'BAR';
$helpers = new HelperCollection;
$this->assertTrue($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$this->assertFalse(isset($helpers->foo));
$this->assertFalse(isset($helpers->bar));
$helpers->foo = $foo;
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$this->assertTrue(isset($helpers->foo));
$this->assertFalse(isset($helpers->bar));
$helpers->bar = $bar;
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$this->assertTrue(isset($helpers->foo));
$this->assertTrue(isset($helpers->bar));
unset($helpers->foo);
$this->assertFalse($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$this->assertFalse(isset($helpers->foo));
$this->assertTrue(isset($helpers->bar));
}
/**
* @dataProvider getInvalidHelperArguments
*/
public function testHelperCollectionIsntAfraidToThrowExceptions($helpers = array(), $actions = array(), $exception = null) {
if ($exception) {
$this->setExpectedException($exception);
}
$helpers = new HelperCollection($helpers);
foreach ($actions as $method => $args) {
call_user_func_array(array($helpers, $method), $args);
}
}
public function getInvalidHelperArguments() {
return array(
array(
'not helpers',
array(),
'\InvalidArgumentException',
),
array(
array(),
array('get' => array('foo')),
'\InvalidArgumentException',
),
array(
array('foo' => 'FOO'),
array('get' => array('foo')),
null,
),
array(
array('foo' => 'FOO'),
array('get' => array('bar')),
'\InvalidArgumentException',
),
array(
array('foo' => 'FOO'),
array(
'add' => array('bar', 'BAR'),
'get' => array('bar'),
),
null,
),
array(
array('foo' => 'FOO'),
array(
'get' => array('foo'),
'remove' => array('foo'),
),
null,
),
array(
array('foo' => 'FOO'),
array(
'remove' => array('foo'),
'get' => array('foo'),
),
'\InvalidArgumentException',
),
array(
array(),
array('remove' => array('foo')),
'\InvalidArgumentException',
),
);
}
}
+50
View File
@@ -43,6 +43,10 @@ class MustacheTest extends \PHPUnit_Framework_TestCase {
'partials' => array(
'foo' => '{{ foo }}',
),
'helpers' => array(
'foo' => function() { return 'foo'; },
'bar' => 'BAR',
),
'charset' => 'ISO-8859-1',
));
@@ -51,6 +55,9 @@ class MustacheTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals('{{ foo }}', $partialsLoader->load('foo'));
$this->assertContains('__whot__', $mustache->getTemplateClassName('{{ foo }}'));
$this->assertEquals('ISO-8859-1', $mustache->getCharset());
$this->assertTrue($mustache->hasHelper('foo'));
$this->assertTrue($mustache->hasHelper('bar'));
$this->assertFalse($mustache->hasHelper('baz'));
}
public function testRender() {
@@ -144,6 +151,49 @@ class MustacheTest extends \PHPUnit_Framework_TestCase {
$mustache->setPartials(array('foo' => '{{ foo }}'));
}
public function testHelpers() {
$foo = function() { return 'foo'; };
$bar = 'BAR';
$mustache = new Mustache(array('helpers' => array(
'foo' => $foo,
'bar' => $bar,
)));
$helpers = $mustache->getHelpers();
$this->assertTrue($mustache->hasHelper('foo'));
$this->assertTrue($mustache->hasHelper('bar'));
$this->assertTrue($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$this->assertSame($foo, $mustache->getHelper('foo'));
$this->assertSame($bar, $mustache->getHelper('bar'));
$mustache->removeHelper('bar');
$this->assertFalse($mustache->hasHelper('bar'));
$mustache->addHelper('bar', $bar);
$this->assertSame($bar, $mustache->getHelper('bar'));
$baz = function($text) { return '__'.$text.'__'; };
$this->assertFalse($mustache->hasHelper('baz'));
$this->assertFalse($helpers->has('baz'));
$mustache->addHelper('baz', $baz);
$this->assertTrue($mustache->hasHelper('baz'));
$this->assertTrue($helpers->has('baz'));
// ... and a functional test
$tpl = $mustache->loadTemplate('{{foo}} - {{bar}} - {{#baz}}qux{{/baz}}');
$this->assertEquals('foo - BAR - __qux__', $tpl->render());
$this->assertEquals('foo - BAR - __qux__', $tpl->render(array('qux' => "won't mess things up")));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testSetHelpersThrowsExceptions() {
$mustache = new Mustache;
$mustache->setHelpers('monkeymonkeymonkey');
}
private static function rmdir($path) {
$path = rtrim($path, '/').'/';
$handle = opendir($path);