Optimizer.php 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2010 Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Twig_NodeVisitor_Optimizer tries to optimizes the AST.
  12. *
  13. * This visitor is always the last registered one.
  14. *
  15. * You can configure which optimizations you want to activate via the
  16. * optimizer mode.
  17. *
  18. * @package twig
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. */
  21. class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
  22. {
  23. const OPTIMIZE_ALL = -1;
  24. const OPTIMIZE_NONE = 0;
  25. const OPTIMIZE_FOR = 2;
  26. const OPTIMIZE_RAW_FILTER = 4;
  27. const OPTIMIZE_VAR_ACCESS = 8;
  28. protected $loops = array();
  29. protected $optimizers;
  30. protected $prependedNodes = array();
  31. protected $inABody = false;
  32. /**
  33. * Constructor.
  34. *
  35. * @param integer $optimizers The optimizer mode
  36. */
  37. public function __construct($optimizers = -1)
  38. {
  39. if (!is_int($optimizers) || $optimizers > 2) {
  40. throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
  41. }
  42. $this->optimizers = $optimizers;
  43. }
  44. /**
  45. * {@inheritdoc}
  46. */
  47. public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
  48. {
  49. if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
  50. $this->enterOptimizeFor($node, $env);
  51. }
  52. if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
  53. if ($this->inABody) {
  54. if (!$node instanceof Twig_Node_Expression) {
  55. if (get_class($node) !== 'Twig_Node') {
  56. array_unshift($this->prependedNodes, array());
  57. }
  58. } else {
  59. $node = $this->optimizeVariables($node, $env);
  60. }
  61. } elseif ($node instanceof Twig_Node_Body) {
  62. $this->inABody = true;
  63. }
  64. }
  65. return $node;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
  71. {
  72. $expression = $node instanceof Twig_Node_Expression;
  73. if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
  74. $this->leaveOptimizeFor($node, $env);
  75. }
  76. if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
  77. $node = $this->optimizeRawFilter($node, $env);
  78. }
  79. $node = $this->optimizePrintNode($node, $env);
  80. if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
  81. if ($node instanceof Twig_Node_Body) {
  82. $this->inABody = false;
  83. } elseif ($this->inABody) {
  84. if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
  85. $nodes = array();
  86. foreach (array_unique($prependedNodes) as $name) {
  87. $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
  88. }
  89. $nodes[] = $node;
  90. $node = new Twig_Node($nodes);
  91. }
  92. }
  93. }
  94. return $node;
  95. }
  96. protected function optimizeVariables($node, $env)
  97. {
  98. if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
  99. $this->prependedNodes[0][] = $node->getAttribute('name');
  100. return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
  101. }
  102. return $node;
  103. }
  104. /**
  105. * Optimizes print nodes.
  106. *
  107. * It replaces:
  108. *
  109. * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
  110. *
  111. * @param Twig_NodeInterface $node A Node
  112. * @param Twig_Environment $env The current Twig environment
  113. */
  114. protected function optimizePrintNode($node, $env)
  115. {
  116. if (!$node instanceof Twig_Node_Print) {
  117. return $node;
  118. }
  119. if (
  120. $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
  121. $node->getNode('expr') instanceof Twig_Node_Expression_Parent
  122. ) {
  123. $node->getNode('expr')->setAttribute('output', true);
  124. return $node->getNode('expr');
  125. }
  126. return $node;
  127. }
  128. /**
  129. * Removes "raw" filters.
  130. *
  131. * @param Twig_NodeInterface $node A Node
  132. * @param Twig_Environment $env The current Twig environment
  133. */
  134. protected function optimizeRawFilter($node, $env)
  135. {
  136. if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
  137. return $node->getNode('node');
  138. }
  139. return $node;
  140. }
  141. /**
  142. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  143. *
  144. * @param Twig_NodeInterface $node A Node
  145. * @param Twig_Environment $env The current Twig environment
  146. */
  147. protected function enterOptimizeFor($node, $env)
  148. {
  149. if ($node instanceof Twig_Node_For) {
  150. // disable the loop variable by default
  151. $node->setAttribute('with_loop', false);
  152. array_unshift($this->loops, $node);
  153. } elseif (!$this->loops) {
  154. // we are outside a loop
  155. return;
  156. }
  157. // when do we need to add the loop variable back?
  158. // the loop variable is referenced for the current loop
  159. elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
  160. $this->addLoopToCurrent();
  161. }
  162. // block reference
  163. elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
  164. $this->addLoopToCurrent();
  165. }
  166. // include without the only attribute
  167. elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
  168. $this->addLoopToAll();
  169. }
  170. // the loop variable is referenced via an attribute
  171. elseif ($node instanceof Twig_Node_Expression_GetAttr
  172. && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
  173. || 'parent' === $node->getNode('attribute')->getAttribute('value')
  174. )
  175. && (true === $this->loops[0]->getAttribute('with_loop')
  176. || ($node->getNode('node') instanceof Twig_Node_Expression_Name
  177. && 'loop' === $node->getNode('node')->getAttribute('name')
  178. )
  179. )
  180. ) {
  181. $this->addLoopToAll();
  182. }
  183. }
  184. /**
  185. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  186. *
  187. * @param Twig_NodeInterface $node A Node
  188. * @param Twig_Environment $env The current Twig environment
  189. */
  190. protected function leaveOptimizeFor($node, $env)
  191. {
  192. if ($node instanceof Twig_Node_For) {
  193. array_shift($this->loops);
  194. }
  195. }
  196. protected function addLoopToCurrent()
  197. {
  198. $this->loops[0]->setAttribute('with_loop', true);
  199. }
  200. protected function addLoopToAll()
  201. {
  202. foreach ($this->loops as $loop) {
  203. $loop->setAttribute('with_loop', true);
  204. }
  205. }
  206. /**
  207. * {@inheritdoc}
  208. */
  209. public function getPriority()
  210. {
  211. return 255;
  212. }
  213. }