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