Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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  }