Twig_Parser::pushBlockStack()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
nc 1
cc 1
eloc 2
nop 1
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
 * Default parser implementation.
15
 *
16
 * @author Fabien Potencier <[email protected]>
17
 */
18
class Twig_Parser implements Twig_ParserInterface
0 ignored issues
show
Deprecated Code introduced by
The interface Twig_ParserInterface has been deprecated with message: since 1.12 (to be removed in 3.0)

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
19
{
20
    protected $stack = array();
21
    protected $stream;
22
    protected $parent;
23
    protected $handlers;
24
    protected $visitors;
25
    protected $expressionParser;
26
    protected $blocks;
27
    protected $blockStack;
28
    protected $macros;
29
    protected $env;
30
    protected $reservedMacroNames;
31
    protected $importedSymbols;
32
    protected $traits;
33
    protected $embeddedTemplates = array();
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param Twig_Environment $env A Twig_Environment instance
39
     */
40
    public function __construct(Twig_Environment $env)
41
    {
42
        $this->env = $env;
43
    }
44
45
    public function getEnvironment()
46
    {
47
        return $this->env;
48
    }
49
50
    public function getVarName()
51
    {
52
        return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
53
    }
54
55
    public function getFilename()
56
    {
57
        return $this->stream->getFilename();
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
64
    {
65
        // push all variables into the stack to keep the current state of the parser
66
        $vars = get_object_vars($this);
67
        unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']);
68
        $this->stack[] = $vars;
69
70
        // tag handlers
71
        if (null === $this->handlers) {
72
            $this->handlers = $this->env->getTokenParsers();
73
            $this->handlers->setParser($this);
74
        }
75
76
        // node visitors
77
        if (null === $this->visitors) {
78
            $this->visitors = $this->env->getNodeVisitors();
79
        }
80
81
        if (null === $this->expressionParser) {
82
            $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators());
83
        }
84
85
        $this->stream = $stream;
86
        $this->parent = null;
87
        $this->blocks = array();
88
        $this->macros = array();
89
        $this->traits = array();
90
        $this->blockStack = array();
91
        $this->importedSymbols = array(array());
92
        $this->embeddedTemplates = array();
93
94
        try {
95
            $body = $this->subparse($test, $dropNeedle);
96
97
            if (null !== $this->parent) {
98
                if (null === $body = $this->filterBodyNodes($body)) {
99
                    $body = new Twig_Node();
100
                }
101
            }
102
        } catch (Twig_Error_Syntax $e) {
103
            if (!$e->getTemplateFile()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $e->getTemplateFile() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
104
                $e->setTemplateFile($this->getFilename());
105
            }
106
107
            if (!$e->getTemplateLine()) {
108
                $e->setTemplateLine($this->stream->getCurrent()->getLine());
109
            }
110
111
            throw $e;
112
        }
113
114
        $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename());
115
116
        $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
117
118
        $node = $traverser->traverse($node);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $traverser->traverse($node); of type null|Twig_NodeInterface|false adds false to the return on line 125 which is incompatible with the return type declared by the interface Twig_ParserInterface::parse of type Twig_Node_Module. It seems like you forgot to handle an error condition.
Loading history...
119
120
        // restore previous stack so previous parse() call can resume working
121
        foreach (array_pop($this->stack) as $key => $val) {
122
            $this->$key = $val;
123
        }
124
125
        return $node;
126
    }
127
128
    public function subparse($test, $dropNeedle = false)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
129
    {
130
        $lineno = $this->getCurrentToken()->getLine();
131
        $rv = array();
132
        while (!$this->stream->isEOF()) {
133
            switch ($this->getCurrentToken()->getType()) {
134
                case Twig_Token::TEXT_TYPE:
135
                    $token = $this->stream->next();
136
                    $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
137
                    break;
138
139
                case Twig_Token::VAR_START_TYPE:
140
                    $token = $this->stream->next();
141
                    $expr = $this->expressionParser->parseExpression();
142
                    $this->stream->expect(Twig_Token::VAR_END_TYPE);
143
                    $rv[] = new Twig_Node_Print($expr, $token->getLine());
144
                    break;
145
146
                case Twig_Token::BLOCK_START_TYPE:
147
                    $this->stream->next();
148
                    $token = $this->getCurrentToken();
149
150
                    if ($token->getType() !== Twig_Token::NAME_TYPE) {
151
                        throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename());
152
                    }
153
154
                    if (null !== $test && call_user_func($test, $token)) {
155
                        if ($dropNeedle) {
156
                            $this->stream->next();
157
                        }
158
159
                        if (1 === count($rv)) {
160
                            return $rv[0];
161
                        }
162
163
                        return new Twig_Node($rv, array(), $lineno);
164
                    }
165
166
                    $subparser = $this->handlers->getTokenParser($token->getValue());
167
                    if (null === $subparser) {
168
                        if (null !== $test) {
169
                            $error = sprintf('Unexpected tag name "%s"', $token->getValue());
170
                            if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
171
                                $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno);
172
                            }
173
174
                            throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename());
175
                        }
176
177
                        $message = sprintf('Unknown tag name "%s"', $token->getValue());
178
                        if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) {
179
                            $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
180
                        }
181
182
                        throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename());
183
                    }
184
185
                    $this->stream->next();
186
187
                    $node = $subparser->parse($token);
188
                    if (null !== $node) {
189
                        $rv[] = $node;
190
                    }
191
                    break;
192
193
                default:
194
                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename());
195
            }
196
        }
197
198
        if (1 === count($rv)) {
199
            return $rv[0];
200
        }
201
202
        return new Twig_Node($rv, array(), $lineno);
203
    }
204
205
    public function addHandler($name, $class)
206
    {
207
        $this->handlers[$name] = $class;
208
    }
209
210
    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
211
    {
212
        $this->visitors[] = $visitor;
213
    }
214
215
    public function getBlockStack()
216
    {
217
        return $this->blockStack;
218
    }
219
220
    public function peekBlockStack()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
221
    {
222
        return $this->blockStack[count($this->blockStack) - 1];
223
    }
224
225
    public function popBlockStack()
226
    {
227
        array_pop($this->blockStack);
228
    }
229
230
    public function pushBlockStack($name)
231
    {
232
        $this->blockStack[] = $name;
233
    }
234
235
    public function hasBlock($name)
236
    {
237
        return isset($this->blocks[$name]);
238
    }
239
240
    public function getBlock($name)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
241
    {
242
        return $this->blocks[$name];
243
    }
244
245
    public function setBlock($name, Twig_Node_Block $value)
246
    {
247
        $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine());
248
    }
249
250
    public function hasMacro($name)
251
    {
252
        return isset($this->macros[$name]);
253
    }
254
255
    public function setMacro($name, Twig_Node_Macro $node)
256
    {
257
        if (null === $this->reservedMacroNames) {
258
            $this->reservedMacroNames = array();
259
            $r = new ReflectionClass($this->env->getBaseTemplateClass());
260
            foreach ($r->getMethods() as $method) {
261
                $this->reservedMacroNames[] = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
262
            }
263
        }
264
265
        if (in_array($name, $this->reservedMacroNames)) {
266
            throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename());
267
        }
268
269
        $this->macros[$name] = $node;
270
    }
271
272
    public function addTrait($trait)
273
    {
274
        $this->traits[] = $trait;
275
    }
276
277
    public function hasTraits()
278
    {
279
        return count($this->traits) > 0;
280
    }
281
282
    public function embedTemplate(Twig_Node_Module $template)
283
    {
284
        $template->setIndex(mt_rand());
285
286
        $this->embeddedTemplates[] = $template;
287
    }
288
289
    public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null)
290
    {
291
        $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node);
292
    }
293
294
    public function getImportedSymbol($type, $alias)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
295
    {
296
        foreach ($this->importedSymbols as $functions) {
297
            if (isset($functions[$type][$alias])) {
298
                return $functions[$type][$alias];
299
            }
300
        }
301
    }
302
303
    public function isMainScope()
304
    {
305
        return 1 === count($this->importedSymbols);
306
    }
307
308
    public function pushLocalScope()
309
    {
310
        array_unshift($this->importedSymbols, array());
311
    }
312
313
    public function popLocalScope()
314
    {
315
        array_shift($this->importedSymbols);
316
    }
317
318
    /**
319
     * Gets the expression parser.
320
     *
321
     * @return Twig_ExpressionParser The expression parser
322
     */
323
    public function getExpressionParser()
324
    {
325
        return $this->expressionParser;
326
    }
327
328
    public function getParent()
329
    {
330
        return $this->parent;
331
    }
332
333
    public function setParent($parent)
334
    {
335
        $this->parent = $parent;
336
    }
337
338
    /**
339
     * Gets the token stream.
340
     *
341
     * @return Twig_TokenStream The token stream
342
     */
343
    public function getStream()
344
    {
345
        return $this->stream;
346
    }
347
348
    /**
349
     * Gets the current token.
350
     *
351
     * @return Twig_Token The current token
352
     */
353
    public function getCurrentToken()
354
    {
355
        return $this->stream->getCurrent();
356
    }
357
358
    protected function filterBodyNodes(Twig_NodeInterface $node)
359
    {
360
        // check that the body does not contain non-empty output nodes
361
        if (
362
            ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
363
            ||
364
            (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
365
        ) {
366
            if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
367
                throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename());
368
            }
369
370
            throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename());
371
        }
372
373
        // bypass "set" nodes as they "capture" the output
374
        if ($node instanceof Twig_Node_Set) {
375
            return $node;
376
        }
377
378
        if ($node instanceof Twig_NodeOutputInterface) {
379
            return;
380
        }
381
382
        foreach ($node as $k => $n) {
383
            if (null !== $n && null === $this->filterBodyNodes($n)) {
384
                $node->removeNode($k);
385
            }
386
        }
387
388
        return $node;
389
    }
390
}
391