* @author Behrooz Shabani * @author Mardix * @copyright 2012 (c) ParsPooyesh Co * @copyright 2013 (c) Behrooz Shabani * @copyright 2014 (c) Mardix * @license MIT * @link http://voodoophp.org/docs/handlebars */ namespace Handlebars; use Handlebars\Loader\StringLoader; use Handlebars\Cache\Dummy; use InvalidArgumentException; class Handlebars { private static $instance = null; const VERSION = '2.2'; const OPTION_ENABLE_DATA_VARIABLES = 'enableDataVariables'; /** * factory method * * @param array $options see __construct's options parameter * * @return Handlebars */ public static function factory($options = array()) { if (! self::$instance) { self::$instance = new self($options); } return self::$instance; } /** * @var Tokenizer */ private $tokenizer; /** * @var Parser */ private $parser; /** * @var Helpers */ private $helpers; /** * @var Loader */ private $loader; /** * @var Loader */ private $partialsLoader; /** * @var Cache */ private $cache; /** * @var callable escape function to use */ private $escape = 'htmlspecialchars'; /** * @var array parametes to pass to escape function */ private $escapeArgs = array( ENT_COMPAT, 'UTF-8' ); private $aliases = array(); /** * @var bool Enable @data variables */ private $enableDataVariables = false; /** * Handlebars engine constructor * $options array can contain : * helpers => Helpers object * escape => a callable function to escape values * escapeArgs => array to pass as extra parameter to escape function * loader => Loader object * partials_loader => Loader object * cache => Cache object * enableDataVariables => boolean. Enables @data variables (default: false) * * @param array $options array of options to set * * @throws \InvalidArgumentException */ public function __construct(Array $options = []) { if (isset($options['helpers'])) { $this->setHelpers($options['helpers']); } if (isset($options['loader'])) { $this->setLoader($options['loader']); } if (isset($options['partials_loader'])) { $this->setPartialsLoader($options['partials_loader']); } if (isset($options['cache'])) { $this->setCache($options['cache']); } if (isset($options['escape'])) { if (!is_callable($options['escape'])) { throw new InvalidArgumentException( 'Handlebars Constructor "escape" option must be callable' ); } $this->escape = $options['escape']; } if (isset($options['escapeArgs'])) { if (!is_array($options['escapeArgs'])) { $options['escapeArgs'] = array($options['escapeArgs']); } $this->escapeArgs = $options['escapeArgs']; } if (isset($options['partials_alias']) && is_array($options['partials_alias']) ) { $this->aliases = $options['partials_alias']; } if (isset($options[self::OPTION_ENABLE_DATA_VARIABLES])) { if (!is_bool($options[self::OPTION_ENABLE_DATA_VARIABLES])) { throw new InvalidArgumentException( 'Handlebars Constructor "' . self::OPTION_ENABLE_DATA_VARIABLES . '" option must be a boolean' ); } $this->enableDataVariables = $options[self::OPTION_ENABLE_DATA_VARIABLES]; } } /** * Shortcut 'render' invocation. * * Equivalent to calling `$handlebars->loadTemplate($template)->render($data);` * * @param string $template template name * @param mixed $data data to use as context * @return string Rendered template */ public function render($template, $data) { return $this->loadTemplate($template)->render($data); } /** * To invoke when this object is called as a function * * @param string $template template name * @param mixed $data data to use as context * @return string Rendered template */ public function __invoke($template, $data) { return $this->render($template, $data); } /** * Set helpers for current enfine * * @param Helpers $helpers handlebars helper * * @return void */ public function setHelpers(Helpers $helpers) { $this->helpers = $helpers; } /** * Get helpers, or create new one if ther is no helper * * @return Helpers */ public function getHelpers() { if (!isset($this->helpers)) { $this->helpers = new Helpers(); } return $this->helpers; } /** * Add a new helper. * * @param string $name helper name * @param mixed $helper helper callable * * @return void */ public function addHelper($name, $helper) { $this->getHelpers()->add($name, $helper); } /** * Get a helper by name. * * @param string $name helper name * @return callable Helper */ public function getHelper($name) { return $this->getHelpers()->__get($name); } /** * Check whether this instance has a helper. * * @param string $name helper name * @return boolean True if the helper is present */ public function hasHelper($name) { return $this->getHelpers()->has($name); } /** * Remove a helper by name. * * @param string $name helper name * @return void */ public function removeHelper($name) { $this->getHelpers()->remove($name); } /** * Set current loader * * @param Loader $loader handlebars loader * @return void */ public function setLoader(Loader $loader) { $this->loader = $loader; } /** * get current loader * * @return Loader */ public function getLoader() { if (! isset($this->loader)) { $this->loader = new StringLoader(); } return $this->loader; } /** * Set current partials loader * * @param Loader $loader handlebars loader * @return void */ public function setPartialsLoader(Loader $loader) { $this->partialsLoader = $loader; } /** * get current partials loader * * @return Loader */ public function getPartialsLoader() { if (!isset($this->partialsLoader)) { $this->partialsLoader = new StringLoader(); } return $this->partialsLoader; } /** * Set cache for current engine * * @param Cache $cache handlebars cache * @return void */ public function setCache(Cache $cache) { $this->cache = $cache; } /** * Get cache * * @return Cache */ public function getCache() { if (!isset($this->cache)) { $this->cache = new Dummy(); } return $this->cache; } /** * Get current escape function * * @return callable */ public function getEscape() { return $this->escape; } /** * Set current escape function * * @param callable $escape function * @throws \InvalidArgumentException * @return void */ public function setEscape($escape) { if (!is_callable($escape)) { throw new InvalidArgumentException( 'Escape function must be a callable' ); } $this->escape = $escape; } /** * Get current escape function * * @return array */ public function getEscapeArgs() { return $this->escapeArgs; } /** * Set current escape function * * @param array $escapeArgs arguments to pass as extra arg to function * @return void */ public function setEscapeArgs($escapeArgs) { if (! is_array($escapeArgs)) { $escapeArgs = array($escapeArgs); } $this->escapeArgs = $escapeArgs; } /** * Set the Handlebars Tokenizer instance. * * @param Tokenizer $tokenizer tokenizer * @return void */ public function setTokenizer(Tokenizer $tokenizer) { $this->tokenizer = $tokenizer; } /** * Get the current Handlebars Tokenizer instance. * * If no Tokenizer instance has been explicitly specified, this method will * instantiate and return a new one. * * @return Tokenizer */ public function getTokenizer() { if (! isset($this->tokenizer)) { $this->tokenizer = new Tokenizer(); } return $this->tokenizer; } /** * Set the Handlebars Parser instance. * * @param Parser $parser parser object * @return void */ public function setParser(Parser $parser) { $this->parser = $parser; } /** * Get the current Handlebars Parser instance. * * If no Parser instance has been explicitly specified, this method will * instantiate and return a new one. * * @return Parser */ public function getParser() { if (! isset($this->parser)) { $this->parser = new Parser(); } return $this->parser; } /** * Determines if the @data variables are enabled. * @return bool */ public function isDataVariablesEnabled() { return $this->enableDataVariables; } /** * Load a template by name with current template loader * * @param string $name template name * * @return Template */ public function loadTemplate($name) { $source = $this->getLoader()->load($name); $tree = $this->tokenize($source); return new Template($this, $tree, $source); } /** * Load a partial by name with current partial loader * * @param string $name partial name * * @return Template */ public function loadPartial($name) { if (isset($this->aliases[$name])) { $name = $this->aliases[$name]; } $source = $this->getPartialsLoader()->load($name); $tree = $this->tokenize($source); return new Template($this, $tree, $source); } /** * Register partial alias * * @param string $alias Partial alias * @param string $content The real value * @return void */ public function registerPartial($alias, $content) { $this->aliases[$alias] = $content; } /** * Un-register partial alias * * @param string $alias Partial alias * @return void */ public function unRegisterPartial($alias) { if (isset($this->aliases[$alias])) { unset($this->aliases[$alias]); } } /** * Load string into a template object * * @param string $source string to load * @return Template */ public function loadString($source) { $tree = $this->tokenize($source); return new Template($this, $tree, $source); } /** * try to tokenize source, or get them from cache if available * * @param string $source handlebars source code * @return array handlebars parsed data into array */ private function tokenize($source) { $hash = md5(sprintf('version: %s, data : %s', self::VERSION, $source)); $tree = $this->getCache()->get($hash); if ($tree === false) { $tokens = $this->getTokenizer()->scan($source); $tree = $this->getParser()->parse($tokens); $this->getCache()->set($hash, $tree); } return $tree; } }