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