Optimizer.php 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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. protected $loops = array();
  28. protected $optimizers;
  29. /**
  30. * Constructor.
  31. *
  32. * @param integer $optimizers The optimizer mode
  33. */
  34. public function __construct($optimizers = -1)
  35. {
  36. if (!is_int($optimizers) || $optimizers > 2) {
  37. throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
  38. }
  39. $this->optimizers = $optimizers;
  40. }
  41. /**
  42. * {@inheritdoc}
  43. */
  44. public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
  45. {
  46. if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
  47. $this->enterOptimizeFor($node, $env);
  48. }
  49. return $node;
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
  55. {
  56. if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
  57. $this->leaveOptimizeFor($node, $env);
  58. }
  59. if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
  60. $node = $this->optimizeRawFilter($node, $env);
  61. }
  62. $node = $this->optimizeRenderBlock($node, $env);
  63. return $node;
  64. }
  65. /**
  66. * Replaces "echo $this->renderBlock()" with "$this->displayBlock()".
  67. *
  68. * @param Twig_NodeInterface $node A Node
  69. * @param Twig_Environment $env The current Twig environment
  70. */
  71. protected function optimizeRenderBlock($node, $env)
  72. {
  73. if ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference) {
  74. $node->getNode('expr')->setAttribute('output', true);
  75. return $node->getNode('expr');
  76. }
  77. return $node;
  78. }
  79. /**
  80. * Removes "raw" filters.
  81. *
  82. * @param Twig_NodeInterface $node A Node
  83. * @param Twig_Environment $env The current Twig environment
  84. */
  85. protected function optimizeRawFilter($node, $env)
  86. {
  87. if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
  88. return $node->getNode('node');
  89. }
  90. return $node;
  91. }
  92. /**
  93. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  94. *
  95. * @param Twig_NodeInterface $node A Node
  96. * @param Twig_Environment $env The current Twig environment
  97. */
  98. protected function enterOptimizeFor($node, $env)
  99. {
  100. if ($node instanceof Twig_Node_For) {
  101. // disable the loop variable by default
  102. $node->setAttribute('with_loop', false);
  103. array_unshift($this->loops, $node);
  104. } elseif (!$this->loops) {
  105. // we are outside a loop
  106. return;
  107. }
  108. // when do we need to add the loop variable back?
  109. // the loop variable is referenced for the current loop
  110. elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
  111. $this->addLoopToCurrent();
  112. }
  113. // block reference
  114. elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
  115. $this->addLoopToCurrent();
  116. }
  117. // include without the only attribute
  118. elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
  119. $this->addLoopToAll();
  120. }
  121. // the loop variable is referenced via an attribute
  122. elseif ($node instanceof Twig_Node_Expression_GetAttr
  123. && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
  124. || 'parent' === $node->getNode('attribute')->getAttribute('value')
  125. )
  126. && (true === $this->loops[0]->getAttribute('with_loop')
  127. || ($node->getNode('node') instanceof Twig_Node_Expression_Name
  128. && 'loop' === $node->getNode('node')->getAttribute('name')
  129. )
  130. )
  131. ) {
  132. $this->addLoopToAll();
  133. }
  134. }
  135. /**
  136. * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
  137. *
  138. * @param Twig_NodeInterface $node A Node
  139. * @param Twig_Environment $env The current Twig environment
  140. */
  141. protected function leaveOptimizeFor($node, $env)
  142. {
  143. if ($node instanceof Twig_Node_For) {
  144. array_shift($this->loops);
  145. }
  146. }
  147. protected function addLoopToCurrent()
  148. {
  149. $this->loops[0]->setAttribute('with_loop', true);
  150. }
  151. protected function addLoopToAll()
  152. {
  153. foreach ($this->loops as $loop) {
  154. $loop->setAttribute('with_loop', true);
  155. }
  156. }
  157. /**
  158. * {@inheritdoc}
  159. */
  160. public function getPriority()
  161. {
  162. return 255;
  163. }
  164. }