|
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->optimizeRenderBlock($node, $env); |
|
73 |
|
74 return $node; |
|
75 } |
|
76 |
|
77 /** |
|
78 * Replaces "echo $this->renderBlock()" with "$this->displayBlock()". |
|
79 * |
|
80 * @param Twig_NodeInterface $node A Node |
|
81 * @param Twig_Environment $env The current Twig environment |
|
82 */ |
|
83 protected function optimizeRenderBlock($node, $env) |
|
84 { |
|
85 if ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference) { |
|
86 $node->getNode('expr')->setAttribute('output', true); |
|
87 |
|
88 return $node->getNode('expr'); |
|
89 } |
|
90 |
|
91 return $node; |
|
92 } |
|
93 |
|
94 /** |
|
95 * Removes "raw" filters. |
|
96 * |
|
97 * @param Twig_NodeInterface $node A Node |
|
98 * @param Twig_Environment $env The current Twig environment |
|
99 */ |
|
100 protected function optimizeRawFilter($node, $env) |
|
101 { |
|
102 if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { |
|
103 return $node->getNode('node'); |
|
104 } |
|
105 |
|
106 return $node; |
|
107 } |
|
108 |
|
109 /** |
|
110 * Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
|
111 * |
|
112 * @param Twig_NodeInterface $node A Node |
|
113 * @param Twig_Environment $env The current Twig environment |
|
114 */ |
|
115 protected function enterOptimizeFor($node, $env) |
|
116 { |
|
117 if ($node instanceof Twig_Node_For) { |
|
118 // disable the loop variable by default |
|
119 $node->setAttribute('with_loop', false); |
|
120 array_unshift($this->loops, $node); |
|
121 } elseif (!$this->loops) { |
|
122 // we are outside a loop |
|
123 return; |
|
124 } |
|
125 |
|
126 // when do we need to add the loop variable back? |
|
127 |
|
128 // the loop variable is referenced for the current loop |
|
129 elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { |
|
130 $this->addLoopToCurrent(); |
|
131 } |
|
132 |
|
133 // block reference |
|
134 elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { |
|
135 $this->addLoopToCurrent(); |
|
136 } |
|
137 |
|
138 // include without the only attribute |
|
139 elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { |
|
140 $this->addLoopToAll(); |
|
141 } |
|
142 |
|
143 // the loop variable is referenced via an attribute |
|
144 elseif ($node instanceof Twig_Node_Expression_GetAttr |
|
145 && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant |
|
146 || 'parent' === $node->getNode('attribute')->getAttribute('value') |
|
147 ) |
|
148 && (true === $this->loops[0]->getAttribute('with_loop') |
|
149 || ($node->getNode('node') instanceof Twig_Node_Expression_Name |
|
150 && 'loop' === $node->getNode('node')->getAttribute('name') |
|
151 ) |
|
152 ) |
|
153 ) { |
|
154 $this->addLoopToAll(); |
|
155 } |
|
156 } |
|
157 |
|
158 /** |
|
159 * Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
|
160 * |
|
161 * @param Twig_NodeInterface $node A Node |
|
162 * @param Twig_Environment $env The current Twig environment |
|
163 */ |
|
164 protected function leaveOptimizeFor($node, $env) |
|
165 { |
|
166 if ($node instanceof Twig_Node_For) { |
|
167 array_shift($this->loops); |
|
168 } |
|
169 } |
|
170 |
|
171 protected function addLoopToCurrent() |
|
172 { |
|
173 $this->loops[0]->setAttribute('with_loop', true); |
|
174 } |
|
175 |
|
176 protected function addLoopToAll() |
|
177 { |
|
178 foreach ($this->loops as $loop) { |
|
179 $loop->setAttribute('with_loop', true); |
|
180 } |
|
181 } |
|
182 |
|
183 /** |
|
184 * {@inheritdoc} |
|
185 */ |
|
186 public function getPriority() |
|
187 { |
|
188 return 255; |
|
189 } |
|
190 } |