diff options
Diffstat (limited to 'inc/Twig/NodeVisitor/Optimizer.php')
-rw-r--r-- | inc/Twig/NodeVisitor/Optimizer.php | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/inc/Twig/NodeVisitor/Optimizer.php b/inc/Twig/NodeVisitor/Optimizer.php new file mode 100644 index 00000000..a254def7 --- /dev/null +++ b/inc/Twig/NodeVisitor/Optimizer.php | |||
@@ -0,0 +1,246 @@ | |||
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 | * @author Fabien Potencier <fabien@symfony.com> | ||
21 | */ | ||
22 | class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface | ||
23 | { | ||
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; | ||
29 | |||
30 | protected $loops = array(); | ||
31 | protected $optimizers; | ||
32 | protected $prependedNodes = array(); | ||
33 | protected $inABody = false; | ||
34 | |||
35 | /** | ||
36 | * Constructor. | ||
37 | * | ||
38 | * @param integer $optimizers The optimizer mode | ||
39 | */ | ||
40 | public function __construct($optimizers = -1) | ||
41 | { | ||
42 | if (!is_int($optimizers) || $optimizers > 2) { | ||
43 | throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); | ||
44 | } | ||
45 | |||
46 | $this->optimizers = $optimizers; | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * {@inheritdoc} | ||
51 | */ | ||
52 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
53 | { | ||
54 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { | ||
55 | $this->enterOptimizeFor($node, $env); | ||
56 | } | ||
57 | |||
58 | if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { | ||
59 | if ($this->inABody) { | ||
60 | if (!$node instanceof Twig_Node_Expression) { | ||
61 | if (get_class($node) !== 'Twig_Node') { | ||
62 | array_unshift($this->prependedNodes, array()); | ||
63 | } | ||
64 | } else { | ||
65 | $node = $this->optimizeVariables($node, $env); | ||
66 | } | ||
67 | } elseif ($node instanceof Twig_Node_Body) { | ||
68 | $this->inABody = true; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | return $node; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * {@inheritdoc} | ||
77 | */ | ||
78 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
79 | { | ||
80 | $expression = $node instanceof Twig_Node_Expression; | ||
81 | |||
82 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { | ||
83 | $this->leaveOptimizeFor($node, $env); | ||
84 | } | ||
85 | |||
86 | if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { | ||
87 | $node = $this->optimizeRawFilter($node, $env); | ||
88 | } | ||
89 | |||
90 | $node = $this->optimizePrintNode($node, $env); | ||
91 | |||
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)) { | ||
97 | $nodes = array(); | ||
98 | foreach (array_unique($prependedNodes) as $name) { | ||
99 | $nodes[] = new Twig_Node_SetTemp($name, $node->getLine()); | ||
100 | } | ||
101 | |||
102 | $nodes[] = $node; | ||
103 | $node = new Twig_Node($nodes); | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | return $node; | ||
109 | } | ||
110 | |||
111 | protected function optimizeVariables($node, $env) | ||
112 | { | ||
113 | if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { | ||
114 | $this->prependedNodes[0][] = $node->getAttribute('name'); | ||
115 | |||
116 | return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); | ||
117 | } | ||
118 | |||
119 | return $node; | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Optimizes print nodes. | ||
124 | * | ||
125 | * It replaces: | ||
126 | * | ||
127 | * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" | ||
128 | * | ||
129 | * @param Twig_NodeInterface $node A Node | ||
130 | * @param Twig_Environment $env The current Twig environment | ||
131 | */ | ||
132 | protected function optimizePrintNode($node, $env) | ||
133 | { | ||
134 | if (!$node instanceof Twig_Node_Print) { | ||
135 | return $node; | ||
136 | } | ||
137 | |||
138 | if ( | ||
139 | $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference || | ||
140 | $node->getNode('expr') instanceof Twig_Node_Expression_Parent | ||
141 | ) { | ||
142 | $node->getNode('expr')->setAttribute('output', true); | ||
143 | |||
144 | return $node->getNode('expr'); | ||
145 | } | ||
146 | |||
147 | return $node; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Removes "raw" filters. | ||
152 | * | ||
153 | * @param Twig_NodeInterface $node A Node | ||
154 | * @param Twig_Environment $env The current Twig environment | ||
155 | */ | ||
156 | protected function optimizeRawFilter($node, $env) | ||
157 | { | ||
158 | if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { | ||
159 | return $node->getNode('node'); | ||
160 | } | ||
161 | |||
162 | return $node; | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. | ||
167 | * | ||
168 | * @param Twig_NodeInterface $node A Node | ||
169 | * @param Twig_Environment $env The current Twig environment | ||
170 | */ | ||
171 | protected function enterOptimizeFor($node, $env) | ||
172 | { | ||
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 | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | // when do we need to add the loop variable back? | ||
183 | |||
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(); | ||
187 | } | ||
188 | |||
189 | // block reference | ||
190 | elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { | ||
191 | $this->addLoopToCurrent(); | ||
192 | } | ||
193 | |||
194 | // include without the only attribute | ||
195 | elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { | ||
196 | $this->addLoopToAll(); | ||
197 | } | ||
198 | |||
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') | ||
203 | ) | ||
204 | && (true === $this->loops[0]->getAttribute('with_loop') | ||
205 | || ($node->getNode('node') instanceof Twig_Node_Expression_Name | ||
206 | && 'loop' === $node->getNode('node')->getAttribute('name') | ||
207 | ) | ||
208 | ) | ||
209 | ) { | ||
210 | $this->addLoopToAll(); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. | ||
216 | * | ||
217 | * @param Twig_NodeInterface $node A Node | ||
218 | * @param Twig_Environment $env The current Twig environment | ||
219 | */ | ||
220 | protected function leaveOptimizeFor($node, $env) | ||
221 | { | ||
222 | if ($node instanceof Twig_Node_For) { | ||
223 | array_shift($this->loops); | ||
224 | } | ||
225 | } | ||
226 | |||
227 | protected function addLoopToCurrent() | ||
228 | { | ||
229 | $this->loops[0]->setAttribute('with_loop', true); | ||
230 | } | ||
231 | |||
232 | protected function addLoopToAll() | ||
233 | { | ||
234 | foreach ($this->loops as $loop) { | ||
235 | $loop->setAttribute('with_loop', true); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * {@inheritdoc} | ||
241 | */ | ||
242 | public function getPriority() | ||
243 | { | ||
244 | return 255; | ||
245 | } | ||
246 | } | ||