1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\NodeVisitor; 13 14use Twig\Environment; 15use Twig\Node\BlockReferenceNode; 16use Twig\Node\BodyNode; 17use Twig\Node\Expression\AbstractExpression; 18use Twig\Node\Expression\BlockReferenceExpression; 19use Twig\Node\Expression\ConstantExpression; 20use Twig\Node\Expression\FilterExpression; 21use Twig\Node\Expression\FunctionExpression; 22use Twig\Node\Expression\GetAttrExpression; 23use Twig\Node\Expression\NameExpression; 24use Twig\Node\Expression\ParentExpression; 25use Twig\Node\Expression\TempNameExpression; 26use Twig\Node\ForNode; 27use Twig\Node\IncludeNode; 28use Twig\Node\Node; 29use Twig\Node\PrintNode; 30use Twig\Node\SetTempNode; 31 32/** 33 * Tries to optimize the AST. 34 * 35 * This visitor is always the last registered one. 36 * 37 * You can configure which optimizations you want to activate via the 38 * optimizer mode. 39 * 40 * @final 41 * 42 * @author Fabien Potencier <fabien@symfony.com> 43 */ 44class OptimizerNodeVisitor extends AbstractNodeVisitor 45{ 46 const OPTIMIZE_ALL = -1; 47 const OPTIMIZE_NONE = 0; 48 const OPTIMIZE_FOR = 2; 49 const OPTIMIZE_RAW_FILTER = 4; 50 const OPTIMIZE_VAR_ACCESS = 8; 51 52 protected $loops = []; 53 protected $loopsTargets = []; 54 protected $optimizers; 55 protected $prependedNodes = []; 56 protected $inABody = false; 57 58 /** 59 * @param int $optimizers The optimizer mode 60 */ 61 public function __construct($optimizers = -1) 62 { 63 if (!\is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { 64 throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); 65 } 66 67 $this->optimizers = $optimizers; 68 } 69 70 protected function doEnterNode(Node $node, Environment $env) 71 { 72 if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { 73 $this->enterOptimizeFor($node, $env); 74 } 75 76 if (\PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { 77 if ($this->inABody) { 78 if (!$node instanceof AbstractExpression) { 79 if ('Twig_Node' !== \get_class($node)) { 80 array_unshift($this->prependedNodes, []); 81 } 82 } else { 83 $node = $this->optimizeVariables($node, $env); 84 } 85 } elseif ($node instanceof BodyNode) { 86 $this->inABody = true; 87 } 88 } 89 90 return $node; 91 } 92 93 protected function doLeaveNode(Node $node, Environment $env) 94 { 95 $expression = $node instanceof AbstractExpression; 96 97 if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { 98 $this->leaveOptimizeFor($node, $env); 99 } 100 101 if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { 102 $node = $this->optimizeRawFilter($node, $env); 103 } 104 105 $node = $this->optimizePrintNode($node, $env); 106 107 if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { 108 if ($node instanceof BodyNode) { 109 $this->inABody = false; 110 } elseif ($this->inABody) { 111 if (!$expression && 'Twig_Node' !== \get_class($node) && $prependedNodes = array_shift($this->prependedNodes)) { 112 $nodes = []; 113 foreach (array_unique($prependedNodes) as $name) { 114 $nodes[] = new SetTempNode($name, $node->getTemplateLine()); 115 } 116 117 $nodes[] = $node; 118 $node = new Node($nodes); 119 } 120 } 121 } 122 123 return $node; 124 } 125 126 protected function optimizeVariables(\Twig_NodeInterface $node, Environment $env) 127 { 128 if ('Twig_Node_Expression_Name' === \get_class($node) && $node->isSimple()) { 129 $this->prependedNodes[0][] = $node->getAttribute('name'); 130 131 return new TempNameExpression($node->getAttribute('name'), $node->getTemplateLine()); 132 } 133 134 return $node; 135 } 136 137 /** 138 * Optimizes print nodes. 139 * 140 * It replaces: 141 * 142 * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" 143 * 144 * @return \Twig_NodeInterface 145 */ 146 protected function optimizePrintNode(\Twig_NodeInterface $node, Environment $env) 147 { 148 if (!$node instanceof PrintNode) { 149 return $node; 150 } 151 152 $exprNode = $node->getNode('expr'); 153 if ( 154 $exprNode instanceof BlockReferenceExpression || 155 $exprNode instanceof ParentExpression 156 ) { 157 $exprNode->setAttribute('output', true); 158 159 return $exprNode; 160 } 161 162 return $node; 163 } 164 165 /** 166 * Removes "raw" filters. 167 * 168 * @return \Twig_NodeInterface 169 */ 170 protected function optimizeRawFilter(\Twig_NodeInterface $node, Environment $env) 171 { 172 if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { 173 return $node->getNode('node'); 174 } 175 176 return $node; 177 } 178 179 /** 180 * Optimizes "for" tag by removing the "loop" variable creation whenever possible. 181 */ 182 protected function enterOptimizeFor(\Twig_NodeInterface $node, Environment $env) 183 { 184 if ($node instanceof ForNode) { 185 // disable the loop variable by default 186 $node->setAttribute('with_loop', false); 187 array_unshift($this->loops, $node); 188 array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); 189 array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); 190 } elseif (!$this->loops) { 191 // we are outside a loop 192 return; 193 } 194 195 // when do we need to add the loop variable back? 196 197 // the loop variable is referenced for the current loop 198 elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { 199 $node->setAttribute('always_defined', true); 200 $this->addLoopToCurrent(); 201 } 202 203 // optimize access to loop targets 204 elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { 205 $node->setAttribute('always_defined', true); 206 } 207 208 // block reference 209 elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) { 210 $this->addLoopToCurrent(); 211 } 212 213 // include without the only attribute 214 elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) { 215 $this->addLoopToAll(); 216 } 217 218 // include function without the with_context=false parameter 219 elseif ($node instanceof FunctionExpression 220 && 'include' === $node->getAttribute('name') 221 && (!$node->getNode('arguments')->hasNode('with_context') 222 || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') 223 ) 224 ) { 225 $this->addLoopToAll(); 226 } 227 228 // the loop variable is referenced via an attribute 229 elseif ($node instanceof GetAttrExpression 230 && (!$node->getNode('attribute') instanceof ConstantExpression 231 || 'parent' === $node->getNode('attribute')->getAttribute('value') 232 ) 233 && (true === $this->loops[0]->getAttribute('with_loop') 234 || ($node->getNode('node') instanceof NameExpression 235 && 'loop' === $node->getNode('node')->getAttribute('name') 236 ) 237 ) 238 ) { 239 $this->addLoopToAll(); 240 } 241 } 242 243 /** 244 * Optimizes "for" tag by removing the "loop" variable creation whenever possible. 245 */ 246 protected function leaveOptimizeFor(\Twig_NodeInterface $node, Environment $env) 247 { 248 if ($node instanceof ForNode) { 249 array_shift($this->loops); 250 array_shift($this->loopsTargets); 251 array_shift($this->loopsTargets); 252 } 253 } 254 255 protected function addLoopToCurrent() 256 { 257 $this->loops[0]->setAttribute('with_loop', true); 258 } 259 260 protected function addLoopToAll() 261 { 262 foreach ($this->loops as $loop) { 263 $loop->setAttribute('with_loop', true); 264 } 265 } 266 267 public function getPriority() 268 { 269 return 255; 270 } 271} 272 273class_alias('Twig\NodeVisitor\OptimizerNodeVisitor', 'Twig_NodeVisitor_Optimizer'); 274