1<?php 2/** 3 * Handlebars 4 * 5 * @category Xamin 6 * @package Handlebars 7 * @author fzerorubigd <fzerorubigd@gmail.com> 8 * @author Behrooz Shabani <everplays@gmail.com> 9 * @author Mardix <https://github.com/mardix> 10 * @copyright 2012 (c) ParsPooyesh Co 11 * @copyright 2013 (c) Behrooz Shabani 12 * @copyright 2014 (c) Mardix 13 * @license MIT 14 * @link http://voodoophp.org/docs/handlebars 15 */ 16 17namespace Handlebars; 18use Handlebars\Loader\StringLoader; 19use Handlebars\Cache\Dummy; 20use InvalidArgumentException; 21 22 23class Handlebars 24{ 25 private static $instance = null; 26 const VERSION = '2.2'; 27 28 const OPTION_ENABLE_DATA_VARIABLES = 'enableDataVariables'; 29 30 /** 31 * factory method 32 * 33 * @param array $options see __construct's options parameter 34 * 35 * @return Handlebars 36 */ 37 public static function factory($options = array()) 38 { 39 if (! self::$instance) { 40 self::$instance = new self($options); 41 } 42 43 return self::$instance; 44 } 45 46 /** 47 * @var Tokenizer 48 */ 49 private $tokenizer; 50 51 /** 52 * @var Parser 53 */ 54 private $parser; 55 56 /** 57 * @var Helpers 58 */ 59 private $helpers; 60 61 /** 62 * @var Loader 63 */ 64 private $loader; 65 66 /** 67 * @var Loader 68 */ 69 private $partialsLoader; 70 71 /** 72 * @var Cache 73 */ 74 private $cache; 75 76 /** 77 * @var callable escape function to use 78 */ 79 private $escape = 'htmlspecialchars'; 80 81 /** 82 * @var array parametes to pass to escape function 83 */ 84 private $escapeArgs = array( 85 ENT_COMPAT, 86 'UTF-8' 87 ); 88 89 private $aliases = array(); 90 91 /** 92 * @var bool Enable @data variables 93 */ 94 private $enableDataVariables = false; 95 96 /** 97 * Handlebars engine constructor 98 * $options array can contain : 99 * helpers => Helpers object 100 * escape => a callable function to escape values 101 * escapeArgs => array to pass as extra parameter to escape function 102 * loader => Loader object 103 * partials_loader => Loader object 104 * cache => Cache object 105 * enableDataVariables => boolean. Enables @data variables (default: false) 106 * 107 * @param array $options array of options to set 108 * 109 * @throws \InvalidArgumentException 110 */ 111 public function __construct(Array $options = []) 112 { 113 if (isset($options['helpers'])) { 114 $this->setHelpers($options['helpers']); 115 } 116 117 if (isset($options['loader'])) { 118 $this->setLoader($options['loader']); 119 } 120 121 if (isset($options['partials_loader'])) { 122 $this->setPartialsLoader($options['partials_loader']); 123 } 124 125 if (isset($options['cache'])) { 126 $this->setCache($options['cache']); 127 } 128 129 if (isset($options['escape'])) { 130 if (!is_callable($options['escape'])) { 131 throw new InvalidArgumentException( 132 'Handlebars Constructor "escape" option must be callable' 133 ); 134 } 135 $this->escape = $options['escape']; 136 } 137 138 if (isset($options['escapeArgs'])) { 139 if (!is_array($options['escapeArgs'])) { 140 $options['escapeArgs'] = array($options['escapeArgs']); 141 } 142 $this->escapeArgs = $options['escapeArgs']; 143 } 144 145 if (isset($options['partials_alias']) 146 && is_array($options['partials_alias']) 147 ) { 148 $this->aliases = $options['partials_alias']; 149 } 150 151 if (isset($options[self::OPTION_ENABLE_DATA_VARIABLES])) { 152 if (!is_bool($options[self::OPTION_ENABLE_DATA_VARIABLES])) { 153 throw new InvalidArgumentException( 154 'Handlebars Constructor "' . self::OPTION_ENABLE_DATA_VARIABLES . '" option must be a boolean' 155 ); 156 } 157 $this->enableDataVariables = $options[self::OPTION_ENABLE_DATA_VARIABLES]; 158 } 159 } 160 161 162 163 /** 164 * Shortcut 'render' invocation. 165 * 166 * Equivalent to calling `$handlebars->loadTemplate($template)->render($data);` 167 * 168 * @param string $template template name 169 * @param mixed $data data to use as context 170 * @return string Rendered template 171 */ 172 public function render($template, $data) 173 { 174 return $this->loadTemplate($template)->render($data); 175 } 176 /** 177 * To invoke when this object is called as a function 178 * 179 * @param string $template template name 180 * @param mixed $data data to use as context 181 * @return string Rendered template 182 */ 183 public function __invoke($template, $data) 184 { 185 return $this->render($template, $data); 186 } 187 188 /** 189 * Set helpers for current enfine 190 * 191 * @param Helpers $helpers handlebars helper 192 * 193 * @return void 194 */ 195 public function setHelpers(Helpers $helpers) 196 { 197 $this->helpers = $helpers; 198 } 199 200 /** 201 * Get helpers, or create new one if ther is no helper 202 * 203 * @return Helpers 204 */ 205 public function getHelpers() 206 { 207 if (!isset($this->helpers)) { 208 $this->helpers = new Helpers(); 209 } 210 return $this->helpers; 211 } 212 213 /** 214 * Add a new helper. 215 * 216 * @param string $name helper name 217 * @param mixed $helper helper callable 218 * 219 * @return void 220 */ 221 public function addHelper($name, $helper) 222 { 223 $this->getHelpers()->add($name, $helper); 224 } 225 226 /** 227 * Get a helper by name. 228 * 229 * @param string $name helper name 230 * @return callable Helper 231 */ 232 public function getHelper($name) 233 { 234 return $this->getHelpers()->__get($name); 235 } 236 237 /** 238 * Check whether this instance has a helper. 239 * 240 * @param string $name helper name 241 * @return boolean True if the helper is present 242 */ 243 public function hasHelper($name) 244 { 245 return $this->getHelpers()->has($name); 246 } 247 248 /** 249 * Remove a helper by name. 250 * 251 * @param string $name helper name 252 * @return void 253 */ 254 public function removeHelper($name) 255 { 256 $this->getHelpers()->remove($name); 257 } 258 259 /** 260 * Set current loader 261 * 262 * @param Loader $loader handlebars loader 263 * @return void 264 */ 265 public function setLoader(Loader $loader) 266 { 267 $this->loader = $loader; 268 } 269 270 /** 271 * get current loader 272 * 273 * @return Loader 274 */ 275 public function getLoader() 276 { 277 if (! isset($this->loader)) { 278 $this->loader = new StringLoader(); 279 } 280 return $this->loader; 281 } 282 283 /** 284 * Set current partials loader 285 * 286 * @param Loader $loader handlebars loader 287 * @return void 288 */ 289 public function setPartialsLoader(Loader $loader) 290 { 291 $this->partialsLoader = $loader; 292 } 293 294 /** 295 * get current partials loader 296 * 297 * @return Loader 298 */ 299 public function getPartialsLoader() 300 { 301 if (!isset($this->partialsLoader)) { 302 $this->partialsLoader = new StringLoader(); 303 } 304 return $this->partialsLoader; 305 } 306 307 /** 308 * Set cache for current engine 309 * 310 * @param Cache $cache handlebars cache 311 * @return void 312 */ 313 public function setCache(Cache $cache) 314 { 315 $this->cache = $cache; 316 } 317 318 /** 319 * Get cache 320 * 321 * @return Cache 322 */ 323 public function getCache() 324 { 325 if (!isset($this->cache)) { 326 $this->cache = new Dummy(); 327 } 328 return $this->cache; 329 } 330 331 /** 332 * Get current escape function 333 * 334 * @return callable 335 */ 336 public function getEscape() 337 { 338 return $this->escape; 339 } 340 341 /** 342 * Set current escape function 343 * 344 * @param callable $escape function 345 * @throws \InvalidArgumentException 346 * @return void 347 */ 348 public function setEscape($escape) 349 { 350 if (!is_callable($escape)) { 351 throw new InvalidArgumentException( 352 'Escape function must be a callable' 353 ); 354 } 355 $this->escape = $escape; 356 } 357 358 /** 359 * Get current escape function 360 * 361 * @return array 362 */ 363 public function getEscapeArgs() 364 { 365 return $this->escapeArgs; 366 } 367 368 /** 369 * Set current escape function 370 * 371 * @param array $escapeArgs arguments to pass as extra arg to function 372 * @return void 373 */ 374 public function setEscapeArgs($escapeArgs) 375 { 376 if (! is_array($escapeArgs)) { 377 $escapeArgs = array($escapeArgs); 378 } 379 $this->escapeArgs = $escapeArgs; 380 } 381 382 383 /** 384 * Set the Handlebars Tokenizer instance. 385 * 386 * @param Tokenizer $tokenizer tokenizer 387 * @return void 388 */ 389 public function setTokenizer(Tokenizer $tokenizer) 390 { 391 $this->tokenizer = $tokenizer; 392 } 393 394 /** 395 * Get the current Handlebars Tokenizer instance. 396 * 397 * If no Tokenizer instance has been explicitly specified, this method will 398 * instantiate and return a new one. 399 * 400 * @return Tokenizer 401 */ 402 public function getTokenizer() 403 { 404 if (! isset($this->tokenizer)) { 405 $this->tokenizer = new Tokenizer(); 406 } 407 408 return $this->tokenizer; 409 } 410 411 /** 412 * Set the Handlebars Parser instance. 413 * 414 * @param Parser $parser parser object 415 * @return void 416 */ 417 public function setParser(Parser $parser) 418 { 419 $this->parser = $parser; 420 } 421 422 /** 423 * Get the current Handlebars Parser instance. 424 * 425 * If no Parser instance has been explicitly specified, this method will 426 * instantiate and return a new one. 427 * 428 * @return Parser 429 */ 430 public function getParser() 431 { 432 if (! isset($this->parser)) { 433 $this->parser = new Parser(); 434 } 435 return $this->parser; 436 } 437 438 /** 439 * Determines if the @data variables are enabled. 440 * @return bool 441 */ 442 public function isDataVariablesEnabled() 443 { 444 return $this->enableDataVariables; 445 } 446 447 /** 448 * Load a template by name with current template loader 449 * 450 * @param string $name template name 451 * 452 * @return Template 453 */ 454 public function loadTemplate($name) 455 { 456 $source = $this->getLoader()->load($name); 457 $tree = $this->tokenize($source); 458 return new Template($this, $tree, $source); 459 } 460 461 /** 462 * Load a partial by name with current partial loader 463 * 464 * @param string $name partial name 465 * 466 * @return Template 467 */ 468 public function loadPartial($name) 469 { 470 if (isset($this->aliases[$name])) { 471 $name = $this->aliases[$name]; 472 } 473 $source = $this->getPartialsLoader()->load($name); 474 $tree = $this->tokenize($source); 475 return new Template($this, $tree, $source); 476 } 477 478 /** 479 * Register partial alias 480 * 481 * @param string $alias Partial alias 482 * @param string $content The real value 483 * @return void 484 */ 485 public function registerPartial($alias, $content) 486 { 487 $this->aliases[$alias] = $content; 488 } 489 490 /** 491 * Un-register partial alias 492 * 493 * @param string $alias Partial alias 494 * @return void 495 */ 496 public function unRegisterPartial($alias) 497 { 498 if (isset($this->aliases[$alias])) { 499 unset($this->aliases[$alias]); 500 } 501 } 502 503 /** 504 * Load string into a template object 505 * 506 * @param string $source string to load 507 * @return Template 508 */ 509 public function loadString($source) 510 { 511 $tree = $this->tokenize($source); 512 return new Template($this, $tree, $source); 513 } 514 515 /** 516 * try to tokenize source, or get them from cache if available 517 * 518 * @param string $source handlebars source code 519 * @return array handlebars parsed data into array 520 */ 521 private function tokenize($source) 522 { 523 $hash = md5(sprintf('version: %s, data : %s', self::VERSION, $source)); 524 $tree = $this->getCache()->get($hash); 525 if ($tree === false) { 526 $tokens = $this->getTokenizer()->scan($source); 527 $tree = $this->getParser()->parse($tokens); 528 $this->getCache()->set($hash, $tree); 529 } 530 return $tree; 531 } 532 533}