[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/mustache/src/Mustache/ -> Compiler.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of Mustache.php.
   5   *
   6   * (c) 2010-2015 Justin Hileman
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  /**
  13   * Mustache Compiler class.
  14   *
  15   * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
  16   */
  17  class Mustache_Compiler
  18  {
  19      private $pragmas;
  20      private $defaultPragmas = array();
  21      private $sections;
  22      private $blocks;
  23      private $source;
  24      private $indentNextLine;
  25      private $customEscape;
  26      private $entityFlags;
  27      private $charset;
  28      private $strictCallables;
  29  
  30      /**
  31       * Compile a Mustache token parse tree into PHP source code.
  32       *
  33       * @param string $source          Mustache Template source code
  34       * @param string $tree            Parse tree of Mustache tokens
  35       * @param string $name            Mustache Template class name
  36       * @param bool   $customEscape    (default: false)
  37       * @param string $charset         (default: 'UTF-8')
  38       * @param bool   $strictCallables (default: false)
  39       * @param int    $entityFlags     (default: ENT_COMPAT)
  40       *
  41       * @return string Generated PHP source code
  42       */
  43      public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
  44      {
  45          $this->pragmas         = $this->defaultPragmas;
  46          $this->sections        = array();
  47          $this->blocks          = array();
  48          $this->source          = $source;
  49          $this->indentNextLine  = true;
  50          $this->customEscape    = $customEscape;
  51          $this->entityFlags     = $entityFlags;
  52          $this->charset         = $charset;
  53          $this->strictCallables = $strictCallables;
  54  
  55          return $this->writeCode($tree, $name);
  56      }
  57  
  58      /**
  59       * Enable pragmas across all templates, regardless of the presence of pragma
  60       * tags in the individual templates.
  61       *
  62       * @internal Users should set global pragmas in Mustache_Engine, not here :)
  63       *
  64       * @param string[] $pragmas
  65       */
  66      public function setPragmas(array $pragmas)
  67      {
  68          $this->pragmas = array();
  69          foreach ($pragmas as $pragma) {
  70              $this->pragmas[$pragma] = true;
  71          }
  72          $this->defaultPragmas = $this->pragmas;
  73      }
  74  
  75      /**
  76       * Helper function for walking the Mustache token parse tree.
  77       *
  78       * @throws Mustache_Exception_SyntaxException upon encountering unknown token types.
  79       *
  80       * @param array $tree  Parse tree of Mustache tokens
  81       * @param int   $level (default: 0)
  82       *
  83       * @return string Generated PHP source code
  84       */
  85      private function walk(array $tree, $level = 0)
  86      {
  87          $code = '';
  88          $level++;
  89          foreach ($tree as $node) {
  90              switch ($node[Mustache_Tokenizer::TYPE]) {
  91                  case Mustache_Tokenizer::T_PRAGMA:
  92                      $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
  93                      break;
  94  
  95                  case Mustache_Tokenizer::T_SECTION:
  96                      $code .= $this->section(
  97                          $node[Mustache_Tokenizer::NODES],
  98                          $node[Mustache_Tokenizer::NAME],
  99                          isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
 100                          $node[Mustache_Tokenizer::INDEX],
 101                          $node[Mustache_Tokenizer::END],
 102                          $node[Mustache_Tokenizer::OTAG],
 103                          $node[Mustache_Tokenizer::CTAG],
 104                          $level
 105                      );
 106                      break;
 107  
 108                  case Mustache_Tokenizer::T_INVERTED:
 109                      $code .= $this->invertedSection(
 110                          $node[Mustache_Tokenizer::NODES],
 111                          $node[Mustache_Tokenizer::NAME],
 112                          isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
 113                          $level
 114                      );
 115                      break;
 116  
 117                  case Mustache_Tokenizer::T_PARTIAL:
 118                      $code .= $this->partial(
 119                          $node[Mustache_Tokenizer::NAME],
 120                          isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
 121                          $level
 122                      );
 123                      break;
 124  
 125                  case Mustache_Tokenizer::T_PARENT:
 126                      $code .= $this->parent(
 127                          $node[Mustache_Tokenizer::NAME],
 128                          isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
 129                          $node[Mustache_Tokenizer::NODES],
 130                          $level
 131                      );
 132                      break;
 133  
 134                  case Mustache_Tokenizer::T_BLOCK_ARG:
 135                      $code .= $this->blockArg(
 136                          $node[Mustache_Tokenizer::NODES],
 137                          $node[Mustache_Tokenizer::NAME],
 138                          $node[Mustache_Tokenizer::INDEX],
 139                          $node[Mustache_Tokenizer::END],
 140                          $node[Mustache_Tokenizer::OTAG],
 141                          $node[Mustache_Tokenizer::CTAG],
 142                          $level
 143                      );
 144                      break;
 145  
 146                  case Mustache_Tokenizer::T_BLOCK_VAR:
 147                      $code .= $this->blockVar(
 148                          $node[Mustache_Tokenizer::NODES],
 149                          $node[Mustache_Tokenizer::NAME],
 150                          $node[Mustache_Tokenizer::INDEX],
 151                          $node[Mustache_Tokenizer::END],
 152                          $node[Mustache_Tokenizer::OTAG],
 153                          $node[Mustache_Tokenizer::CTAG],
 154                          $level
 155                      );
 156                      break;
 157  
 158                  case Mustache_Tokenizer::T_COMMENT:
 159                      break;
 160  
 161                  case Mustache_Tokenizer::T_ESCAPED:
 162                  case Mustache_Tokenizer::T_UNESCAPED:
 163                  case Mustache_Tokenizer::T_UNESCAPED_2:
 164                      $code .= $this->variable(
 165                          $node[Mustache_Tokenizer::NAME],
 166                          isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
 167                          $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
 168                          $level
 169                      );
 170                      break;
 171  
 172                  case Mustache_Tokenizer::T_TEXT:
 173                      $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
 174                      break;
 175  
 176                  default:
 177                      throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
 178              }
 179          }
 180  
 181          return $code;
 182      }
 183  
 184      const KLASS = '<?php
 185  
 186          class %s extends Mustache_Template
 187          {
 188              private $lambdaHelper;%s
 189  
 190              public function renderInternal(Mustache_Context $context, $indent = \'\')
 191              {
 192                  $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
 193                  $buffer = \'\';
 194                  $newContext = array();
 195          %s
 196  
 197                  return $buffer;
 198              }
 199          %s
 200          %s
 201          }';
 202  
 203      const KLASS_NO_LAMBDAS = '<?php
 204  
 205          class %s extends Mustache_Template
 206          {%s
 207              public function renderInternal(Mustache_Context $context, $indent = \'\')
 208              {
 209                  $buffer = \'\';
 210                  $newContext = array();
 211          %s
 212  
 213                  return $buffer;
 214              }
 215          }';
 216  
 217      const STRICT_CALLABLE = 'protected $strictCallables = true;';
 218  
 219      /**
 220       * Generate Mustache Template class PHP source.
 221       *
 222       * @param array  $tree Parse tree of Mustache tokens
 223       * @param string $name Mustache Template class name
 224       *
 225       * @return string Generated PHP source code
 226       */
 227      private function writeCode($tree, $name)
 228      {
 229          $code     = $this->walk($tree);
 230          $sections = implode("\n", $this->sections);
 231          $blocks   = implode("\n", $this->blocks);
 232          $klass    = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
 233  
 234          $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
 235  
 236          return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks);
 237      }
 238  
 239      const BLOCK_VAR = '
 240          $blockFunction = $context->findInBlock(%s);
 241          if (is_callable($blockFunction)) {
 242              $buffer .= call_user_func($blockFunction, $context);
 243          } else {%s
 244          }
 245      ';
 246  
 247      /**
 248       * Generate Mustache Template inheritance block variable PHP source.
 249       *
 250       * @param array  $nodes Array of child tokens
 251       * @param string $id    Section name
 252       * @param int    $start Section start offset
 253       * @param int    $end   Section end offset
 254       * @param string $otag  Current Mustache opening tag
 255       * @param string $ctag  Current Mustache closing tag
 256       * @param int    $level
 257       *
 258       * @return string Generated PHP source code
 259       */
 260      private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
 261      {
 262          $id = var_export($id, true);
 263  
 264          return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes, $level));
 265      }
 266  
 267      const BLOCK_ARG = '$newContext[%s] = array($this, \'block%s\');';
 268  
 269      /**
 270       * Generate Mustache Template inheritance block argument PHP source.
 271       *
 272       * @param array  $nodes Array of child tokens
 273       * @param string $id    Section name
 274       * @param int    $start Section start offset
 275       * @param int    $end   Section end offset
 276       * @param string $otag  Current Mustache opening tag
 277       * @param string $ctag  Current Mustache closing tag
 278       * @param int    $level
 279       *
 280       * @return string Generated PHP source code
 281       */
 282      private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
 283      {
 284          $key = $this->block($nodes);
 285          $keystr = var_export($key, true);
 286          $id = var_export($id, true);
 287  
 288          return sprintf($this->prepare(self::BLOCK_ARG, 1), $id, $key);
 289      }
 290  
 291      const BLOCK_FUNCTION = '
 292          public function block%s($context)
 293          {
 294              $indent = $buffer = \'\';%s
 295  
 296              return $buffer;
 297          }
 298      ';
 299  
 300      /**
 301       * Generate Mustache Template inheritance block function PHP source.
 302       *
 303       * @param array $nodes Array of child tokens
 304       *
 305       * @return string key of new block function
 306       */
 307      private function block($nodes)
 308      {
 309          $code = $this->walk($nodes, 0);
 310          $key = ucfirst(md5($code));
 311  
 312          if (!isset($this->blocks[$key])) {
 313              $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
 314          }
 315  
 316          return $key;
 317      }
 318  
 319      const SECTION_CALL = '
 320          // %s section
 321          $value = $context->%s(%s);%s
 322          $buffer .= $this->section%s($context, $indent, $value);
 323      ';
 324  
 325      const SECTION = '
 326          private function section%s(Mustache_Context $context, $indent, $value)
 327          {
 328              $buffer = \'\';
 329              if (%s) {
 330                  $source = %s;
 331                  $result = call_user_func($value, $source, %s);
 332                  if (strpos($result, \'{{\') === false) {
 333                      $buffer .= $result;
 334                  } else {
 335                      $buffer .= $this->mustache
 336                          ->loadLambda((string) $result%s)
 337                          ->renderInternal($context);
 338                  }
 339              } elseif (!empty($value)) {
 340                  $values = $this->isIterable($value) ? $value : array($value);
 341                  foreach ($values as $value) {
 342                      $context->push($value);
 343                      %s
 344                      $context->pop();
 345                  }
 346              }
 347  
 348              return $buffer;
 349          }
 350      ';
 351  
 352      /**
 353       * Generate Mustache Template section PHP source.
 354       *
 355       * @param array    $nodes   Array of child tokens
 356       * @param string   $id      Section name
 357       * @param string[] $filters Array of filters
 358       * @param int      $start   Section start offset
 359       * @param int      $end     Section end offset
 360       * @param string   $otag    Current Mustache opening tag
 361       * @param string   $ctag    Current Mustache closing tag
 362       * @param int      $level
 363       * @param bool     $arg     (default: false)
 364       *
 365       * @return string Generated section PHP source code
 366       */
 367      private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level, $arg = false)
 368      {
 369          $source   = var_export(substr($this->source, $start, $end - $start), true);
 370          $callable = $this->getCallable();
 371  
 372          if ($otag !== '{{' || $ctag !== '}}') {
 373              $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
 374              $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag);
 375              $delims = ', ' . $delimTag;
 376          } else {
 377              $helper = '$this->lambdaHelper';
 378              $delims = '';
 379          }
 380  
 381          $key = ucfirst(md5($delims . "\n" . $source));
 382  
 383          if (!isset($this->sections[$key])) {
 384              $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2));
 385          }
 386  
 387          if ($arg === true) {
 388              return $key;
 389          } else {
 390              $method  = $this->getFindMethod($id);
 391              $id      = var_export($id, true);
 392              $filters = $this->getFilters($filters, $level);
 393  
 394              return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
 395          }
 396      }
 397  
 398      const INVERTED_SECTION = '
 399          // %s inverted section
 400          $value = $context->%s(%s);%s
 401          if (empty($value)) {
 402              %s
 403          }
 404      ';
 405  
 406      /**
 407       * Generate Mustache Template inverted section PHP source.
 408       *
 409       * @param array    $nodes   Array of child tokens
 410       * @param string   $id      Section name
 411       * @param string[] $filters Array of filters
 412       * @param int      $level
 413       *
 414       * @return string Generated inverted section PHP source code
 415       */
 416      private function invertedSection($nodes, $id, $filters, $level)
 417      {
 418          $method  = $this->getFindMethod($id);
 419          $id      = var_export($id, true);
 420          $filters = $this->getFilters($filters, $level);
 421  
 422          return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
 423      }
 424  
 425      const PARTIAL_INDENT = ', $indent . %s';
 426      const PARTIAL = '
 427          if ($partial = $this->mustache->loadPartial(%s)) {
 428              $buffer .= $partial->renderInternal($context%s);
 429          }
 430      ';
 431  
 432      /**
 433       * Generate Mustache Template partial call PHP source.
 434       *
 435       * @param string $id     Partial name
 436       * @param string $indent Whitespace indent to apply to partial
 437       * @param int    $level
 438       *
 439       * @return string Generated partial call PHP source code
 440       */
 441      private function partial($id, $indent, $level)
 442      {
 443          if ($indent !== '') {
 444              $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
 445          } else {
 446              $indentParam = '';
 447          }
 448  
 449          return sprintf(
 450              $this->prepare(self::PARTIAL, $level),
 451              var_export($id, true),
 452              $indentParam
 453          );
 454      }
 455  
 456      const PARENT = '
 457          %s
 458  
 459          if ($parent = $this->mustache->LoadPartial(%s)) {
 460              $context->pushBlockContext($newContext);
 461              $buffer .= $parent->renderInternal($context, $indent);
 462              $context->popBlockContext();
 463          }
 464      ';
 465  
 466      /**
 467       * Generate Mustache Template inheritance parent call PHP source.
 468       *
 469       * @param string $id       Parent tag name
 470       * @param string $indent   Whitespace indent to apply to parent
 471       * @param array  $children Child nodes
 472       * @param int    $level
 473       *
 474       * @return string Generated PHP source code
 475       */
 476      private function parent($id, $indent, array $children, $level)
 477      {
 478          $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
 479  
 480          return sprintf(
 481              $this->prepare(self::PARENT, $level),
 482              $this->walk($realChildren, $level),
 483              var_export($id, true),
 484              var_export($indent, true)
 485          );
 486      }
 487  
 488      /**
 489       * Helper method for filtering out non-block-arg tokens.
 490       *
 491       * @param array $node
 492       *
 493       * @return bool True if $node is a block arg token.
 494       */
 495      private static function onlyBlockArgs(array $node)
 496      {
 497          return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
 498      }
 499  
 500      const VARIABLE = '
 501          $value = $this->resolveValue($context->%s(%s), $context);%s
 502          $buffer .= %s%s;
 503      ';
 504  
 505      /**
 506       * Generate Mustache Template variable interpolation PHP source.
 507       *
 508       * @param string   $id      Variable name
 509       * @param string[] $filters Array of filters
 510       * @param bool     $escape  Escape the variable value for output?
 511       * @param int      $level
 512       *
 513       * @return string Generated variable interpolation PHP source
 514       */
 515      private function variable($id, $filters, $escape, $level)
 516      {
 517          $method  = $this->getFindMethod($id);
 518          $id      = ($method !== 'last') ? var_export($id, true) : '';
 519          $filters = $this->getFilters($filters, $level);
 520          $value   = $escape ? $this->getEscape() : '$value';
 521  
 522          return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
 523      }
 524  
 525      const FILTER = '
 526          $filter = $context->%s(%s);
 527          if (!(%s)) {
 528              throw new Mustache_Exception_UnknownFilterException(%s);
 529          }
 530          $value = call_user_func($filter, $value);%s
 531      ';
 532  
 533      /**
 534       * Generate Mustache Template variable filtering PHP source.
 535       *
 536       * @param string[] $filters Array of filters
 537       * @param int      $level
 538       *
 539       * @return string Generated filter PHP source
 540       */
 541      private function getFilters(array $filters, $level)
 542      {
 543          if (empty($filters)) {
 544              return '';
 545          }
 546  
 547          $name     = array_shift($filters);
 548          $method   = $this->getFindMethod($name);
 549          $filter   = ($method !== 'last') ? var_export($name, true) : '';
 550          $callable = $this->getCallable('$filter');
 551          $msg      = var_export($name, true);
 552  
 553          return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
 554      }
 555  
 556      const LINE = '$buffer .= "\n";';
 557      const TEXT = '$buffer .= %s%s;';
 558  
 559      /**
 560       * Generate Mustache Template output Buffer call PHP source.
 561       *
 562       * @param string $text
 563       * @param int    $level
 564       *
 565       * @return string Generated output Buffer call PHP source
 566       */
 567      private function text($text, $level)
 568      {
 569          $indentNextLine = (substr($text, -1) === "\n");
 570          $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
 571          $this->indentNextLine = $indentNextLine;
 572  
 573          return $code;
 574      }
 575  
 576      /**
 577       * Prepare PHP source code snippet for output.
 578       *
 579       * @param string $text
 580       * @param int    $bonus          Additional indent level (default: 0)
 581       * @param bool   $prependNewline Prepend a newline to the snippet? (default: true)
 582       * @param bool   $appendNewline  Append a newline to the snippet? (default: false)
 583       *
 584       * @return string PHP source code snippet
 585       */
 586      private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
 587      {
 588          $text = ($prependNewline ? "\n" : '') . trim($text);
 589          if ($prependNewline) {
 590              $bonus++;
 591          }
 592          if ($appendNewline) {
 593              $text .= "\n";
 594          }
 595  
 596          return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text);
 597      }
 598  
 599      const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
 600      const CUSTOM_ESCAPE  = 'call_user_func($this->mustache->getEscape(), %s)';
 601  
 602      /**
 603       * Get the current escaper.
 604       *
 605       * @param string $value (default: '$value')
 606       *
 607       * @return string Either a custom callback, or an inline call to `htmlspecialchars`
 608       */
 609      private function getEscape($value = '$value')
 610      {
 611          if ($this->customEscape) {
 612              return sprintf(self::CUSTOM_ESCAPE, $value);
 613          }
 614  
 615          return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
 616      }
 617  
 618      /**
 619       * Select the appropriate Context `find` method for a given $id.
 620       *
 621       * The return value will be one of `find`, `findDot` or `last`.
 622       *
 623       * @see Mustache_Context::find
 624       * @see Mustache_Context::findDot
 625       * @see Mustache_Context::last
 626       *
 627       * @param string $id Variable name
 628       *
 629       * @return string `find` method name
 630       */
 631      private function getFindMethod($id)
 632      {
 633          if ($id === '.') {
 634              return 'last';
 635          }
 636  
 637          if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) {
 638              if (substr($id, 0, 1) === '.') {
 639                  return 'findAnchoredDot';
 640              }
 641          }
 642  
 643          if (strpos($id, '.') === false) {
 644              return 'find';
 645          }
 646  
 647          return 'findDot';
 648      }
 649  
 650      const IS_CALLABLE        = '!is_string(%s) && is_callable(%s)';
 651      const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
 652  
 653      /**
 654       * Helper function to compile strict vs lax "is callable" logic.
 655       *
 656       * @param string $variable (default: '$value')
 657       *
 658       * @return string "is callable" logic
 659       */
 660      private function getCallable($variable = '$value')
 661      {
 662          $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
 663  
 664          return sprintf($tpl, $variable, $variable);
 665      }
 666  
 667      const LINE_INDENT = '$indent . ';
 668  
 669      /**
 670       * Get the current $indent prefix to write to the buffer.
 671       *
 672       * @return string "$indent . " or ""
 673       */
 674      private function flushIndent()
 675      {
 676          if (!$this->indentNextLine) {
 677              return '';
 678          }
 679  
 680          $this->indentNextLine = false;
 681  
 682          return self::LINE_INDENT;
 683      }
 684  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1