Optimizer.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. * @author Fabien Potencier <fabien@symfony.com>
  19. */
  20. class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor
  21. {
  22. const OPTIMIZE_ALL = -1;
  23. const OPTIMIZE_NONE = 0;
  24. const OPTIMIZE_FOR = 2;
  25. const OPTIMIZE_RAW_FILTER = 4;
  26. const OPTIMIZE_VAR_ACCESS = 8;
  27. protected $loops = array();
  28. protected $loopsTargets = array();
  29. protected $optimizers;
  30. protected $prependedNodes = array();
  31. protected $inABody = false;
  32. /**
  33. * Constructor.
  34. *
  35. * @param int $optimizers The optimizer mode
  36. */
  37. public function __construct($optimizers = -1)
  38. {
  39. if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) {
  40. throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
  41. }
  42. $this->optimizers = $optimizers;
  43. }
  44. /**
  45. * {@inheritdoc}
  46. */
  47. protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
  48. {
  49. if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
  50. $this->enterOptimizeFor($node, $env);
  51. }
  52. if (PHP_VERSION_ID < 50400 && 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. protected function doLeaveNode(Twig_Node $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(Twig_NodeInterface $node, Twig_Environment $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. * @return Twig_NodeInterface
  115. */
  116. protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env)
  117. {
  118. if (!$node instanceof Twig_Node_Print) {
  119. return $node;
  120. }
  121. if (
  122. $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
  123. $node->getNode('expr') instanceof Twig_Node_Expression_Parent
  124. ) {
  125. $node->getNode('expr')->setAttribute('output', true);
  126. return $node->getNode('expr');
  127. }
  128. return $node;
  129. }
  130. /**
  131. * Removes "raw" filters.
  132. *
  133. * @param Twig_NodeInterface $node A Node
  134. * @param Twig_Environment $env The current Twig environment
  135. *
  136. * @return Twig_NodeInterface
  137. */
  138. protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env)
  139. {
  140. if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
  141. return $node->getNode('node');
  142. }
  143. return $node;
  144. }
  145. /**
  146. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  147. *
  148. * @param Twig_NodeInterface $node A Node
  149. * @param Twig_Environment $env The current Twig environment
  150. */
  151. protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
  152. {
  153. if ($node instanceof Twig_Node_For) {
  154. // disable the loop variable by default
  155. $node->setAttribute('with_loop', false);
  156. array_unshift($this->loops, $node);
  157. array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name'));
  158. array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name'));
  159. } elseif (!$this->loops) {
  160. // we are outside a loop
  161. return;
  162. }
  163. // when do we need to add the loop variable back?
  164. // the loop variable is referenced for the current loop
  165. elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
  166. $node->setAttribute('always_defined', true);
  167. $this->addLoopToCurrent();
  168. }
  169. // optimize access to loop targets
  170. elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) {
  171. $node->setAttribute('always_defined', true);
  172. }
  173. // block reference
  174. elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
  175. $this->addLoopToCurrent();
  176. }
  177. // include without the only attribute
  178. elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
  179. $this->addLoopToAll();
  180. }
  181. // include function without the with_context=false parameter
  182. elseif ($node instanceof Twig_Node_Expression_Function
  183. && 'include' === $node->getAttribute('name')
  184. && (!$node->getNode('arguments')->hasNode('with_context')
  185. || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')
  186. )
  187. ) {
  188. $this->addLoopToAll();
  189. }
  190. // the loop variable is referenced via an attribute
  191. elseif ($node instanceof Twig_Node_Expression_GetAttr
  192. && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
  193. || 'parent' === $node->getNode('attribute')->getAttribute('value')
  194. )
  195. && (true === $this->loops[0]->getAttribute('with_loop')
  196. || ($node->getNode('node') instanceof Twig_Node_Expression_Name
  197. && 'loop' === $node->getNode('node')->getAttribute('name')
  198. )
  199. )
  200. ) {
  201. $this->addLoopToAll();
  202. }
  203. }
  204. /**
  205. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  206. *
  207. * @param Twig_NodeInterface $node A Node
  208. * @param Twig_Environment $env The current Twig environment
  209. */
  210. protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
  211. {
  212. if ($node instanceof Twig_Node_For) {
  213. array_shift($this->loops);
  214. array_shift($this->loopsTargets);
  215. array_shift($this->loopsTargets);
  216. }
  217. }
  218. protected function addLoopToCurrent()
  219. {
  220. $this->loops[0]->setAttribute('with_loop', true);
  221. }
  222. protected function addLoopToAll()
  223. {
  224. foreach ($this->loops as $loop) {
  225. $loop->setAttribute('with_loop', true);
  226. }
  227. }
  228. /**
  229. * {@inheritdoc}
  230. */
  231. public function getPriority()
  232. {
  233. return 255;
  234. }
  235. }