4 * This file is part of Twig.
6 * (c) 2010 Fabien Potencier
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
13 * Twig_NodeVisitor_Optimizer tries to optimizes the AST.
15 * This visitor is always the last registered one.
17 * You can configure which optimizations you want to activate via the
20 * @author Fabien Potencier <fabien@symfony.com>
22 class Twig_NodeVisitor_Optimizer
implements Twig_NodeVisitorInterface
24 const OPTIMIZE_ALL
= -1;
25 const OPTIMIZE_NONE
= 0;
26 const OPTIMIZE_FOR
= 2;
27 const OPTIMIZE_RAW_FILTER
= 4;
28 const OPTIMIZE_VAR_ACCESS
= 8;
30 protected $loops = array();
31 protected $optimizers;
32 protected $prependedNodes = array();
33 protected $inABody = false;
38 * @param integer $optimizers The optimizer mode
40 public function __construct($optimizers = -1)
42 if (!is_int($optimizers) || $optimizers > 2) {
43 throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
46 $this->optimizers
= $optimizers;
52 public function enterNode(Twig_NodeInterface
$node, Twig_Environment
$env)
54 if (self
::OPTIMIZE_FOR
=== (self
::OPTIMIZE_FOR
& $this->optimizers
)) {
55 $this->enterOptimizeFor($node, $env);
58 if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self
::OPTIMIZE_VAR_ACCESS
=== (self
::OPTIMIZE_VAR_ACCESS
& $this->optimizers
) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
60 if (!$node instanceof Twig_Node_Expression
) {
61 if (get_class($node) !== 'Twig_Node') {
62 array_unshift($this->prependedNodes
, array());
65 $node = $this->optimizeVariables($node, $env);
67 } elseif ($node instanceof Twig_Node_Body
) {
68 $this->inABody
= true;
78 public function leaveNode(Twig_NodeInterface
$node, Twig_Environment
$env)
80 $expression = $node instanceof Twig_Node_Expression
;
82 if (self
::OPTIMIZE_FOR
=== (self
::OPTIMIZE_FOR
& $this->optimizers
)) {
83 $this->leaveOptimizeFor($node, $env);
86 if (self
::OPTIMIZE_RAW_FILTER
=== (self
::OPTIMIZE_RAW_FILTER
& $this->optimizers
)) {
87 $node = $this->optimizeRawFilter($node, $env);
90 $node = $this->optimizePrintNode($node, $env);
92 if (self
::OPTIMIZE_VAR_ACCESS
=== (self
::OPTIMIZE_VAR_ACCESS
& $this->optimizers
) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
93 if ($node instanceof Twig_Node_Body
) {
94 $this->inABody
= false;
95 } elseif ($this->inABody
) {
96 if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes
)) {
98 foreach (array_unique($prependedNodes) as $name) {
99 $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
103 $node = new Twig_Node($nodes);
111 protected function optimizeVariables($node, $env)
113 if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
114 $this->prependedNodes
[0][] = $node->getAttribute('name');
116 return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
123 * Optimizes print nodes.
127 * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
129 * @param Twig_NodeInterface $node A Node
130 * @param Twig_Environment $env The current Twig environment
132 protected function optimizePrintNode($node, $env)
134 if (!$node instanceof Twig_Node_Print
) {
139 $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference
||
140 $node->getNode('expr') instanceof Twig_Node_Expression_Parent
142 $node->getNode('expr')->setAttribute('output', true);
144 return $node->getNode('expr');
151 * Removes "raw" filters.
153 * @param Twig_NodeInterface $node A Node
154 * @param Twig_Environment $env The current Twig environment
156 protected function optimizeRawFilter($node, $env)
158 if ($node instanceof Twig_Node_Expression_Filter
&& 'raw' == $node->getNode('filter')->getAttribute('value')) {
159 return $node->getNode('node');
166 * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
168 * @param Twig_NodeInterface $node A Node
169 * @param Twig_Environment $env The current Twig environment
171 protected function enterOptimizeFor($node, $env)
173 if ($node instanceof Twig_Node_For
) {
174 // disable the loop variable by default
175 $node->setAttribute('with_loop', false);
176 array_unshift($this->loops
, $node);
177 } elseif (!$this->loops
) {
178 // we are outside a loop
182 // when do we need to add the loop variable back?
184 // the loop variable is referenced for the current loop
185 elseif ($node instanceof Twig_Node_Expression_Name
&& 'loop' === $node->getAttribute('name')) {
186 $this->addLoopToCurrent();
190 elseif ($node instanceof Twig_Node_BlockReference
|| $node instanceof Twig_Node_Expression_BlockReference
) {
191 $this->addLoopToCurrent();
194 // include without the only attribute
195 elseif ($node instanceof Twig_Node_Include
&& !$node->getAttribute('only')) {
196 $this->addLoopToAll();
199 // the loop variable is referenced via an attribute
200 elseif ($node instanceof Twig_Node_Expression_GetAttr
201 && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
202 || 'parent' === $node->getNode('attribute')->getAttribute('value')
204 && (true === $this->loops
[0]->getAttribute('with_loop')
205 || ($node->getNode('node') instanceof Twig_Node_Expression_Name
206 && 'loop' === $node->getNode('node')->getAttribute('name')
210 $this->addLoopToAll();
215 * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
217 * @param Twig_NodeInterface $node A Node
218 * @param Twig_Environment $env The current Twig environment
220 protected function leaveOptimizeFor($node, $env)
222 if ($node instanceof Twig_Node_For
) {
223 array_shift($this->loops
);
227 protected function addLoopToCurrent()
229 $this->loops
[0]->setAttribute('with_loop', true);
232 protected function addLoopToAll()
234 foreach ($this->loops
as $loop) {
235 $loop->setAttribute('with_loop', true);
242 public function getPriority()