vendor/twig/twig/src/Compiler.php line 102

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. * (c) Armin Ronacher
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Twig;
  12. use Twig\Node\Node;
  13. /**
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class Compiler
  17. {
  18. private $lastLine;
  19. private $source;
  20. private $indentation;
  21. private $debugInfo = [];
  22. private $sourceOffset;
  23. private $sourceLine;
  24. private $varNameSalt = 0;
  25. private $didUseEcho = false;
  26. private $didUseEchoStack = [];
  27. public function __construct(
  28. private Environment $env,
  29. ) {
  30. }
  31. public function getEnvironment(): Environment
  32. {
  33. return $this->env;
  34. }
  35. public function getSource(): string
  36. {
  37. return $this->source;
  38. }
  39. /**
  40. * @return $this
  41. */
  42. public function reset(int $indentation = 0)
  43. {
  44. $this->lastLine = null;
  45. $this->source = '';
  46. $this->debugInfo = [];
  47. $this->sourceOffset = 0;
  48. // source code starts at 1 (as we then increment it when we encounter new lines)
  49. $this->sourceLine = 1;
  50. $this->indentation = $indentation;
  51. $this->varNameSalt = 0;
  52. return $this;
  53. }
  54. /**
  55. * @return $this
  56. */
  57. public function compile(Node $node, int $indentation = 0)
  58. {
  59. $this->reset($indentation);
  60. $this->didUseEchoStack[] = $this->didUseEcho;
  61. try {
  62. $this->didUseEcho = false;
  63. $node->compile($this);
  64. if ($this->didUseEcho) {
  65. trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class);
  66. }
  67. return $this;
  68. } finally {
  69. $this->didUseEcho = array_pop($this->didUseEchoStack);
  70. }
  71. }
  72. /**
  73. * @return $this
  74. */
  75. public function subcompile(Node $node, bool $raw = true)
  76. {
  77. if (!$raw) {
  78. $this->source .= str_repeat(' ', $this->indentation * 4);
  79. }
  80. $this->didUseEchoStack[] = $this->didUseEcho;
  81. try {
  82. $this->didUseEcho = false;
  83. $node->compile($this);
  84. if ($this->didUseEcho) {
  85. trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class);
  86. }
  87. return $this;
  88. } finally {
  89. $this->didUseEcho = array_pop($this->didUseEchoStack);
  90. }
  91. }
  92. /**
  93. * Adds a raw string to the compiled code.
  94. *
  95. * @return $this
  96. */
  97. public function raw(string $string)
  98. {
  99. $this->checkForEcho($string);
  100. $this->source .= $string;
  101. return $this;
  102. }
  103. /**
  104. * Writes a string to the compiled code by adding indentation.
  105. *
  106. * @return $this
  107. */
  108. public function write(...$strings)
  109. {
  110. foreach ($strings as $string) {
  111. $this->checkForEcho($string);
  112. $this->source .= str_repeat(' ', $this->indentation * 4).$string;
  113. }
  114. return $this;
  115. }
  116. /**
  117. * Adds a quoted string to the compiled code.
  118. *
  119. * @return $this
  120. */
  121. public function string(string $value)
  122. {
  123. // Single quotes are encoded as \x27 (not \') as a defense-in-depth measure:
  124. // it guarantees that the compiled output never contains a literal "'" derived
  125. // from user input, which prevents breaking out of a surrounding single-quoted
  126. // PHP context if a caller mistakenly concatenates the result into one.
  127. // \' is not a recognized escape sequence in PHP double-quoted strings (the
  128. // backslash would be kept literally), so \x27 is used instead.
  129. $this->source .= \sprintf('"%s"', str_replace("'", '\\x27', addcslashes($value, "\0\t\"\$\\")));
  130. return $this;
  131. }
  132. /**
  133. * Returns a PHP representation of a given value.
  134. *
  135. * @return $this
  136. */
  137. public function repr($value)
  138. {
  139. if (\is_int($value) || \is_float($value)) {
  140. if (false !== $locale = setlocale(\LC_NUMERIC, '0')) {
  141. setlocale(\LC_NUMERIC, 'C');
  142. }
  143. $this->raw(var_export($value, true));
  144. if (false !== $locale) {
  145. setlocale(\LC_NUMERIC, $locale);
  146. }
  147. } elseif (null === $value) {
  148. $this->raw('null');
  149. } elseif (\is_bool($value)) {
  150. $this->raw($value ? 'true' : 'false');
  151. } elseif (\is_array($value)) {
  152. $this->raw('[');
  153. $first = true;
  154. foreach ($value as $key => $v) {
  155. if (!$first) {
  156. $this->raw(', ');
  157. }
  158. $first = false;
  159. $this->repr($key);
  160. $this->raw(' => ');
  161. $this->repr($v);
  162. }
  163. $this->raw(']');
  164. } else {
  165. $this->string($value);
  166. }
  167. return $this;
  168. }
  169. /**
  170. * @return $this
  171. */
  172. public function addDebugInfo(Node $node)
  173. {
  174. if ($node->getTemplateLine() != $this->lastLine) {
  175. $this->write(\sprintf("// line %d\n", $node->getTemplateLine()));
  176. $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
  177. $this->sourceOffset = \strlen($this->source);
  178. $this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
  179. $this->lastLine = $node->getTemplateLine();
  180. }
  181. return $this;
  182. }
  183. public function getDebugInfo(): array
  184. {
  185. ksort($this->debugInfo);
  186. return $this->debugInfo;
  187. }
  188. /**
  189. * @return $this
  190. */
  191. public function indent(int $step = 1)
  192. {
  193. $this->indentation += $step;
  194. return $this;
  195. }
  196. /**
  197. * @return $this
  198. *
  199. * @throws \LogicException When trying to outdent too much so the indentation would become negative
  200. */
  201. public function outdent(int $step = 1)
  202. {
  203. // can't outdent by more steps than the current indentation level
  204. if ($this->indentation < $step) {
  205. throw new \LogicException('Unable to call outdent() as the indentation would become negative.');
  206. }
  207. $this->indentation -= $step;
  208. return $this;
  209. }
  210. public function getVarName(): string
  211. {
  212. return \sprintf('_v%d', $this->varNameSalt++);
  213. }
  214. private function checkForEcho(string $string): void
  215. {
  216. if ($this->didUseEcho) {
  217. return;
  218. }
  219. $this->didUseEcho = preg_match('/^\s*+(echo|print)\b/', $string, $m) ? $m[1] : false;
  220. }
  221. }