Project

General

Profile

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
 * Consider this class as being final. If you need to customize the behavior of
17
 * the generated class, consider adding nodes to the following nodes: display_start,
18
 * display_end, constructor_start, constructor_end, and class_end.
19
 *
20
 * @author Fabien Potencier <fabien@symfony.com>
21
 */
22
class Twig_Node_Module extends Twig_Node
23
{
24
    public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename)
25
    {
26
        // embedded templates are set as attributes so that they are only visited once by the visitors
27
        parent::__construct(array(
28
            'parent' => $parent,
29
            'body' => $body,
30
            'blocks' => $blocks,
31
            'macros' => $macros,
32
            'traits' => $traits,
33
            'display_start' => new Twig_Node(),
34
            'display_end' => new Twig_Node(),
35
            'constructor_start' => new Twig_Node(),
36
            'constructor_end' => new Twig_Node(),
37
            'class_end' => new Twig_Node(),
38
        ), array(
39
            'filename' => $filename,
40
            'index' => null,
41
            'embedded_templates' => $embeddedTemplates,
42
        ), 1);
43
    }
44

    
45
    public function setIndex($index)
46
    {
47
        $this->setAttribute('index', $index);
48
    }
49

    
50
    public function compile(Twig_Compiler $compiler)
51
    {
52
        $this->compileTemplate($compiler);
53

    
54
        foreach ($this->getAttribute('embedded_templates') as $template) {
55
            $compiler->subcompile($template);
56
        }
57
    }
58

    
59
    protected function compileTemplate(Twig_Compiler $compiler)
60
    {
61
        if (!$this->getAttribute('index')) {
62
            $compiler->write('<?php');
63
        }
64

    
65
        $this->compileClassHeader($compiler);
66

    
67
        if (
68
            count($this->getNode('blocks'))
69
            || count($this->getNode('traits'))
70
            || null === $this->getNode('parent')
71
            || $this->getNode('parent') instanceof Twig_Node_Expression_Constant
72
            || count($this->getNode('constructor_start'))
73
            || count($this->getNode('constructor_end'))
74
        ) {
75
            $this->compileConstructor($compiler);
76
        }
77

    
78
        $this->compileGetParent($compiler);
79

    
80
        $this->compileDisplay($compiler);
81

    
82
        $compiler->subcompile($this->getNode('blocks'));
83

    
84
        $this->compileMacros($compiler);
85

    
86
        $this->compileGetTemplateName($compiler);
87

    
88
        $this->compileIsTraitable($compiler);
89

    
90
        $this->compileDebugInfo($compiler);
91

    
92
        $this->compileClassFooter($compiler);
93
    }
94

    
95
    protected function compileGetParent(Twig_Compiler $compiler)
96
    {
97
        if (null === $parent = $this->getNode('parent')) {
98
            return;
99
        }
100

    
101
        $compiler
102
            ->write("protected function doGetParent(array \$context)\n", "{\n")
103
            ->indent()
104
            ->addDebugInfo($parent)
105
            ->write('return ')
106
        ;
107

    
108
        if ($parent instanceof Twig_Node_Expression_Constant) {
109
            $compiler->subcompile($parent);
110
        } else {
111
            $compiler
112
                ->raw('$this->loadTemplate(')
113
                ->subcompile($parent)
114
                ->raw(', ')
115
                ->repr($compiler->getFilename())
116
                ->raw(', ')
117
                ->repr($this->getNode('parent')->getLine())
118
                ->raw(')')
119
            ;
120
        }
121

    
122
        $compiler
123
            ->raw(";\n")
124
            ->outdent()
125
            ->write("}\n\n")
126
        ;
127
    }
128

    
129
    protected function compileClassHeader(Twig_Compiler $compiler)
130
    {
131
        $compiler
132
            ->write("\n\n")
133
            // if the filename contains */, add a blank to avoid a PHP parse error
134
            ->write('/* '.str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
135
            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
136
            ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
137
            ->write("{\n")
138
            ->indent()
139
        ;
140
    }
141

    
142
    protected function compileConstructor(Twig_Compiler $compiler)
143
    {
144
        $compiler
145
            ->write("public function __construct(Twig_Environment \$env)\n", "{\n")
146
            ->indent()
147
            ->subcompile($this->getNode('constructor_start'))
148
            ->write("parent::__construct(\$env);\n\n")
149
        ;
150

    
151
        // parent
152
        if (null === $parent = $this->getNode('parent')) {
153
            $compiler->write("\$this->parent = false;\n\n");
154
        } elseif ($parent instanceof Twig_Node_Expression_Constant) {
155
            $compiler
156
                ->addDebugInfo($parent)
157
                ->write('$this->parent = $this->loadTemplate(')
158
                ->subcompile($parent)
159
                ->raw(', ')
160
                ->repr($compiler->getFilename())
161
                ->raw(', ')
162
                ->repr($this->getNode('parent')->getLine())
163
                ->raw(");\n")
164
            ;
165
        }
166

    
167
        $countTraits = count($this->getNode('traits'));
168
        if ($countTraits) {
169
            // traits
170
            foreach ($this->getNode('traits') as $i => $trait) {
171
                $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i));
172

    
173
                $compiler
174
                    ->addDebugInfo($trait->getNode('template'))
175
                    ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
176
                    ->indent()
177
                    ->write("throw new Twig_Error_Runtime('Template \"'.")
178
                    ->subcompile($trait->getNode('template'))
179
                    ->raw(".'\" cannot be used as a trait.');\n")
180
                    ->outdent()
181
                    ->write("}\n")
182
                    ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
183
                ;
184

    
185
                foreach ($trait->getNode('targets') as $key => $value) {
186
                    $compiler
187
                        ->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
188
                        ->string($key)
189
                        ->raw("])) {\n")
190
                        ->indent()
191
                        ->write("throw new Twig_Error_Runtime(sprintf('Block ")
192
                        ->string($key)
193
                        ->raw(' is not defined in trait ')
194
                        ->subcompile($trait->getNode('template'))
195
                        ->raw(".'));\n")
196
                        ->outdent()
197
                        ->write("}\n\n")
198

    
199
                        ->write(sprintf('$_trait_%s_blocks[', $i))
200
                        ->subcompile($value)
201
                        ->raw(sprintf('] = $_trait_%s_blocks[', $i))
202
                        ->string($key)
203
                        ->raw(sprintf(']; unset($_trait_%s_blocks[', $i))
204
                        ->string($key)
205
                        ->raw("]);\n\n")
206
                    ;
207
                }
208
            }
209

    
210
            if ($countTraits > 1) {
211
                $compiler
212
                    ->write("\$this->traits = array_merge(\n")
213
                    ->indent()
214
                ;
215

    
216
                for ($i = 0; $i < $countTraits; ++$i) {
217
                    $compiler
218
                        ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i))
219
                    ;
220
                }
221

    
222
                $compiler
223
                    ->outdent()
224
                    ->write(");\n\n")
225
                ;
226
            } else {
227
                $compiler
228
                    ->write("\$this->traits = \$_trait_0_blocks;\n\n")
229
                ;
230
            }
231

    
232
            $compiler
233
                ->write("\$this->blocks = array_merge(\n")
234
                ->indent()
235
                ->write("\$this->traits,\n")
236
                ->write("array(\n")
237
            ;
238
        } else {
239
            $compiler
240
                ->write("\$this->blocks = array(\n")
241
            ;
242
        }
243

    
244
        // blocks
245
        $compiler
246
            ->indent()
247
        ;
248

    
249
        foreach ($this->getNode('blocks') as $name => $node) {
250
            $compiler
251
                ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name))
252
            ;
253
        }
254

    
255
        if ($countTraits) {
256
            $compiler
257
                ->outdent()
258
                ->write(")\n")
259
            ;
260
        }
261

    
262
        $compiler
263
            ->outdent()
264
            ->write(");\n")
265
            ->outdent()
266
            ->subcompile($this->getNode('constructor_end'))
267
            ->write("}\n\n")
268
        ;
269
    }
270

    
271
    protected function compileDisplay(Twig_Compiler $compiler)
272
    {
273
        $compiler
274
            ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n")
275
            ->indent()
276
            ->subcompile($this->getNode('display_start'))
277
            ->subcompile($this->getNode('body'))
278
        ;
279

    
280
        if (null !== $parent = $this->getNode('parent')) {
281
            $compiler->addDebugInfo($parent);
282
            if ($parent instanceof Twig_Node_Expression_Constant) {
283
                $compiler->write('$this->parent');
284
            } else {
285
                $compiler->write('$this->getParent($context)');
286
            }
287
            $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
288
        }
289

    
290
        $compiler
291
            ->subcompile($this->getNode('display_end'))
292
            ->outdent()
293
            ->write("}\n\n")
294
        ;
295
    }
296

    
297
    protected function compileClassFooter(Twig_Compiler $compiler)
298
    {
299
        $compiler
300
            ->subcompile($this->getNode('class_end'))
301
            ->outdent()
302
            ->write("}\n")
303
        ;
304
    }
305

    
306
    protected function compileMacros(Twig_Compiler $compiler)
307
    {
308
        $compiler->subcompile($this->getNode('macros'));
309
    }
310

    
311
    protected function compileGetTemplateName(Twig_Compiler $compiler)
312
    {
313
        $compiler
314
            ->write("public function getTemplateName()\n", "{\n")
315
            ->indent()
316
            ->write('return ')
317
            ->repr($this->getAttribute('filename'))
318
            ->raw(";\n")
319
            ->outdent()
320
            ->write("}\n\n")
321
        ;
322
    }
323

    
324
    protected function compileIsTraitable(Twig_Compiler $compiler)
325
    {
326
        // A template can be used as a trait if:
327
        //   * it has no parent
328
        //   * it has no macros
329
        //   * it has no body
330
        //
331
        // Put another way, a template can be used as a trait if it
332
        // only contains blocks and use statements.
333
        $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros'));
334
        if ($traitable) {
335
            if ($this->getNode('body') instanceof Twig_Node_Body) {
336
                $nodes = $this->getNode('body')->getNode(0);
337
            } else {
338
                $nodes = $this->getNode('body');
339
            }
340

    
341
            if (!count($nodes)) {
342
                $nodes = new Twig_Node(array($nodes));
343
            }
344

    
345
            foreach ($nodes as $node) {
346
                if (!count($node)) {
347
                    continue;
348
                }
349

    
350
                if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
351
                    continue;
352
                }
353

    
354
                if ($node instanceof Twig_Node_BlockReference) {
355
                    continue;
356
                }
357

    
358
                $traitable = false;
359
                break;
360
            }
361
        }
362

    
363
        if ($traitable) {
364
            return;
365
        }
366

    
367
        $compiler
368
            ->write("public function isTraitable()\n", "{\n")
369
            ->indent()
370
            ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
371
            ->outdent()
372
            ->write("}\n\n")
373
        ;
374
    }
375

    
376
    protected function compileDebugInfo(Twig_Compiler $compiler)
377
    {
378
        $compiler
379
            ->write("public function getDebugInfo()\n", "{\n")
380
            ->indent()
381
            ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
382
            ->outdent()
383
            ->write("}\n")
384
        ;
385
    }
386

    
387
    protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var)
388
    {
389
        if ($node instanceof Twig_Node_Expression_Constant) {
390
            $compiler
391
                ->write(sprintf('%s = $this->loadTemplate(', $var))
392
                ->subcompile($node)
393
                ->raw(', ')
394
                ->repr($compiler->getFilename())
395
                ->raw(', ')
396
                ->repr($node->getLine())
397
                ->raw(");\n")
398
            ;
399
        } else {
400
            throw new LogicException('Trait templates can only be constant nodes');
401
        }
402
    }
403
}
(16-16/23)