]>
Commit | Line | Data |
---|---|---|
a4565e88 NL |
1 | <?php |
2 | ||
3 | /* | |
4 | * This file is part of Twig. | |
5 | * | |
6 | * (c) 2009 Fabien Potencier | |
7 | * (c) 2009 Armin Ronacher | |
8 | * | |
9 | * For the full copyright and license information, please view the LICENSE | |
10 | * file that was distributed with this source code. | |
11 | */ | |
12 | ||
13 | /** | |
14 | * Represents a module node. | |
15 | * | |
16 | * @author Fabien Potencier <fabien@symfony.com> | |
17 | */ | |
18 | class Twig_Node_Module extends Twig_Node | |
19 | { | |
20 | public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename) | |
21 | { | |
22 | // embedded templates are set as attributes so that they are only visited once by the visitors | |
23 | parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1); | |
24 | } | |
25 | ||
26 | public function setIndex($index) | |
27 | { | |
28 | $this->setAttribute('index', $index); | |
29 | } | |
30 | ||
31 | /** | |
32 | * Compiles the node to PHP. | |
33 | * | |
34 | * @param Twig_Compiler A Twig_Compiler instance | |
35 | */ | |
36 | public function compile(Twig_Compiler $compiler) | |
37 | { | |
38 | $this->compileTemplate($compiler); | |
39 | ||
40 | foreach ($this->getAttribute('embedded_templates') as $template) { | |
41 | $compiler->subcompile($template); | |
42 | } | |
43 | } | |
44 | ||
45 | protected function compileTemplate(Twig_Compiler $compiler) | |
46 | { | |
47 | if (!$this->getAttribute('index')) { | |
48 | $compiler->write('<?php'); | |
49 | } | |
50 | ||
51 | $this->compileClassHeader($compiler); | |
52 | ||
53 | if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | |
54 | $this->compileConstructor($compiler); | |
55 | } | |
56 | ||
57 | $this->compileGetParent($compiler); | |
58 | ||
59 | $this->compileDisplayHeader($compiler); | |
60 | ||
61 | $this->compileDisplayBody($compiler); | |
62 | ||
63 | $this->compileDisplayFooter($compiler); | |
64 | ||
65 | $compiler->subcompile($this->getNode('blocks')); | |
66 | ||
67 | $this->compileMacros($compiler); | |
68 | ||
69 | $this->compileGetTemplateName($compiler); | |
70 | ||
71 | $this->compileIsTraitable($compiler); | |
72 | ||
73 | $this->compileDebugInfo($compiler); | |
74 | ||
75 | $this->compileClassFooter($compiler); | |
76 | } | |
77 | ||
78 | protected function compileGetParent(Twig_Compiler $compiler) | |
79 | { | |
80 | if (null === $this->getNode('parent')) { | |
81 | return; | |
82 | } | |
83 | ||
84 | $compiler | |
85 | ->write("protected function doGetParent(array \$context)\n", "{\n") | |
86 | ->indent() | |
87 | ->write("return ") | |
88 | ; | |
89 | ||
90 | if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | |
91 | $compiler->subcompile($this->getNode('parent')); | |
92 | } else { | |
93 | $compiler | |
94 | ->raw("\$this->env->resolveTemplate(") | |
95 | ->subcompile($this->getNode('parent')) | |
96 | ->raw(")") | |
97 | ; | |
98 | } | |
99 | ||
100 | $compiler | |
101 | ->raw(";\n") | |
102 | ->outdent() | |
103 | ->write("}\n\n") | |
104 | ; | |
105 | } | |
106 | ||
107 | protected function compileDisplayBody(Twig_Compiler $compiler) | |
108 | { | |
109 | $compiler->subcompile($this->getNode('body')); | |
110 | ||
111 | if (null !== $this->getNode('parent')) { | |
112 | if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | |
113 | $compiler->write("\$this->parent"); | |
114 | } else { | |
115 | $compiler->write("\$this->getParent(\$context)"); | |
116 | } | |
117 | $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); | |
118 | } | |
119 | } | |
120 | ||
121 | protected function compileClassHeader(Twig_Compiler $compiler) | |
122 | { | |
123 | $compiler | |
124 | ->write("\n\n") | |
125 | // if the filename contains */, add a blank to avoid a PHP parse error | |
126 | ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n") | |
127 | ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index'))) | |
128 | ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) | |
129 | ->write("{\n") | |
130 | ->indent() | |
131 | ; | |
132 | } | |
133 | ||
134 | protected function compileConstructor(Twig_Compiler $compiler) | |
135 | { | |
136 | $compiler | |
137 | ->write("public function __construct(Twig_Environment \$env)\n", "{\n") | |
138 | ->indent() | |
139 | ->write("parent::__construct(\$env);\n\n") | |
140 | ; | |
141 | ||
142 | // parent | |
143 | if (null === $this->getNode('parent')) { | |
144 | $compiler->write("\$this->parent = false;\n\n"); | |
145 | } elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | |
146 | $compiler | |
147 | ->write("\$this->parent = \$this->env->loadTemplate(") | |
148 | ->subcompile($this->getNode('parent')) | |
149 | ->raw(");\n\n") | |
150 | ; | |
151 | } | |
152 | ||
153 | $countTraits = count($this->getNode('traits')); | |
154 | if ($countTraits) { | |
155 | // traits | |
156 | foreach ($this->getNode('traits') as $i => $trait) { | |
157 | $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); | |
158 | ||
159 | $compiler | |
160 | ->addDebugInfo($trait->getNode('template')) | |
161 | ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) | |
162 | ->indent() | |
163 | ->write("throw new Twig_Error_Runtime('Template \"'.") | |
164 | ->subcompile($trait->getNode('template')) | |
165 | ->raw(".'\" cannot be used as a trait.');\n") | |
166 | ->outdent() | |
167 | ->write("}\n") | |
168 | ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) | |
169 | ; | |
170 | ||
171 | foreach ($trait->getNode('targets') as $key => $value) { | |
172 | $compiler | |
173 | ->write(sprintf("\$_trait_%s_blocks[", $i)) | |
174 | ->subcompile($value) | |
175 | ->raw(sprintf("] = \$_trait_%s_blocks[", $i)) | |
176 | ->string($key) | |
177 | ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i)) | |
178 | ->string($key) | |
179 | ->raw("]);\n\n") | |
180 | ; | |
181 | } | |
182 | } | |
183 | ||
184 | if ($countTraits > 1) { | |
185 | $compiler | |
186 | ->write("\$this->traits = array_merge(\n") | |
187 | ->indent() | |
188 | ; | |
189 | ||
190 | for ($i = 0; $i < $countTraits; $i++) { | |
191 | $compiler | |
192 | ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i)) | |
193 | ; | |
194 | } | |
195 | ||
196 | $compiler | |
197 | ->outdent() | |
198 | ->write(");\n\n") | |
199 | ; | |
200 | } else { | |
201 | $compiler | |
202 | ->write("\$this->traits = \$_trait_0_blocks;\n\n") | |
203 | ; | |
204 | } | |
205 | ||
206 | $compiler | |
207 | ->write("\$this->blocks = array_merge(\n") | |
208 | ->indent() | |
209 | ->write("\$this->traits,\n") | |
210 | ->write("array(\n") | |
211 | ; | |
212 | } else { | |
213 | $compiler | |
214 | ->write("\$this->blocks = array(\n") | |
215 | ; | |
216 | } | |
217 | ||
218 | // blocks | |
219 | $compiler | |
220 | ->indent() | |
221 | ; | |
222 | ||
223 | foreach ($this->getNode('blocks') as $name => $node) { | |
224 | $compiler | |
225 | ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name)) | |
226 | ; | |
227 | } | |
228 | ||
229 | if ($countTraits) { | |
230 | $compiler | |
231 | ->outdent() | |
232 | ->write(")\n") | |
233 | ; | |
234 | } | |
235 | ||
236 | $compiler | |
237 | ->outdent() | |
238 | ->write(");\n") | |
239 | ->outdent() | |
240 | ->write("}\n\n"); | |
241 | ; | |
242 | } | |
243 | ||
244 | protected function compileDisplayHeader(Twig_Compiler $compiler) | |
245 | { | |
246 | $compiler | |
247 | ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") | |
248 | ->indent() | |
249 | ; | |
250 | } | |
251 | ||
252 | protected function compileDisplayFooter(Twig_Compiler $compiler) | |
253 | { | |
254 | $compiler | |
255 | ->outdent() | |
256 | ->write("}\n\n") | |
257 | ; | |
258 | } | |
259 | ||
260 | protected function compileClassFooter(Twig_Compiler $compiler) | |
261 | { | |
262 | $compiler | |
263 | ->outdent() | |
264 | ->write("}\n") | |
265 | ; | |
266 | } | |
267 | ||
268 | protected function compileMacros(Twig_Compiler $compiler) | |
269 | { | |
270 | $compiler->subcompile($this->getNode('macros')); | |
271 | } | |
272 | ||
273 | protected function compileGetTemplateName(Twig_Compiler $compiler) | |
274 | { | |
275 | $compiler | |
276 | ->write("public function getTemplateName()\n", "{\n") | |
277 | ->indent() | |
278 | ->write('return ') | |
279 | ->repr($this->getAttribute('filename')) | |
280 | ->raw(";\n") | |
281 | ->outdent() | |
282 | ->write("}\n\n") | |
283 | ; | |
284 | } | |
285 | ||
286 | protected function compileIsTraitable(Twig_Compiler $compiler) | |
287 | { | |
288 | // A template can be used as a trait if: | |
289 | // * it has no parent | |
290 | // * it has no macros | |
291 | // * it has no body | |
292 | // | |
293 | // Put another way, a template can be used as a trait if it | |
294 | // only contains blocks and use statements. | |
295 | $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); | |
296 | if ($traitable) { | |
297 | if ($this->getNode('body') instanceof Twig_Node_Body) { | |
298 | $nodes = $this->getNode('body')->getNode(0); | |
299 | } else { | |
300 | $nodes = $this->getNode('body'); | |
301 | } | |
302 | ||
303 | if (!count($nodes)) { | |
304 | $nodes = new Twig_Node(array($nodes)); | |
305 | } | |
306 | ||
307 | foreach ($nodes as $node) { | |
308 | if (!count($node)) { | |
309 | continue; | |
310 | } | |
311 | ||
312 | if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { | |
313 | continue; | |
314 | } | |
315 | ||
316 | if ($node instanceof Twig_Node_BlockReference) { | |
317 | continue; | |
318 | } | |
319 | ||
320 | $traitable = false; | |
321 | break; | |
322 | } | |
323 | } | |
324 | ||
325 | if ($traitable) { | |
326 | return; | |
327 | } | |
328 | ||
329 | $compiler | |
330 | ->write("public function isTraitable()\n", "{\n") | |
331 | ->indent() | |
332 | ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) | |
333 | ->outdent() | |
334 | ->write("}\n\n") | |
335 | ; | |
336 | } | |
337 | ||
338 | protected function compileDebugInfo(Twig_Compiler $compiler) | |
339 | { | |
340 | $compiler | |
341 | ->write("public function getDebugInfo()\n", "{\n") | |
342 | ->indent() | |
343 | ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) | |
344 | ->outdent() | |
345 | ->write("}\n") | |
346 | ; | |
347 | } | |
348 | ||
349 | protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) | |
350 | { | |
351 | if ($node instanceof Twig_Node_Expression_Constant) { | |
352 | $compiler | |
353 | ->write(sprintf("%s = \$this->env->loadTemplate(", $var)) | |
354 | ->subcompile($node) | |
355 | ->raw(");\n") | |
356 | ; | |
357 | } else { | |
358 | $compiler | |
359 | ->write(sprintf("%s = ", $var)) | |
360 | ->subcompile($node) | |
361 | ->raw(";\n") | |
362 | ->write(sprintf("if (!%s", $var)) | |
363 | ->raw(" instanceof Twig_Template) {\n") | |
364 | ->indent() | |
365 | ->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var)) | |
366 | ->outdent() | |
367 | ->write("}\n") | |
368 | ; | |
369 | } | |
370 | } | |
371 | } |