This PR was merged into the 1.x branch.
Discussion
----------
Move tests handling in Twig_ParserExpression instead of Twig_Extension_Core
In my quest to better organize the layers in Twig, I've always find the way we deal with test a bit awkward. This PR moves the code from the Core extension to the Parser, where it belongs, even if if makes hardcoding 'is' and 'is not' twice, but I think SOC is more important here. Especially as I want to remove the `getEnvironment()` from the Parser class at some point. Having everything centralized helps a lot.
Commits
-------
4deb03a deprecated Twig_Parser::getEnvironment()
0d6686e moved tests handling in Twig_ParserExpression instead of Twig_Extension_Core
... | ... |
@@ -1,5 +1,6 @@ |
1 | 1 |
* 1.27.0 (2016-XX-XX) |
2 | 2 |
|
3 |
+ * deprecated Twig_Parser::getEnvironment() |
|
3 | 4 |
* deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() |
4 | 5 |
* deprecated Twig_Compiler::addIndentation() |
5 | 6 |
* fixed regression when registering two extensions having the same class name |
... | ... |
@@ -23,6 +23,8 @@ Token Parsers |
23 | 23 |
* As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token |
24 | 24 |
parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead. |
25 | 25 |
|
26 |
+* As of Twig 1.27, ``Twig_Parser::getEnvironment()`` is deprecated. |
|
27 |
+ |
|
26 | 28 |
Extensions |
27 | 29 |
---------- |
28 | 30 |
|
... | ... |
@@ -19,6 +19,8 @@ |
19 | 19 |
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser |
20 | 20 |
* |
21 | 21 |
* @author Fabien Potencier <fabien@symfony.com> |
22 |
+ * |
|
23 |
+ * @internal |
|
22 | 24 |
*/ |
23 | 25 |
class Twig_ExpressionParser |
24 | 26 |
{ |
... | ... |
@@ -29,11 +31,23 @@ class Twig_ExpressionParser |
29 | 31 |
protected $unaryOperators; |
30 | 32 |
protected $binaryOperators; |
31 | 33 |
|
32 |
- public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) |
|
34 |
+ private $env; |
|
35 |
+ |
|
36 |
+ public function __construct(Twig_Parser $parser, Twig_Environment $env = null) |
|
33 | 37 |
{ |
34 | 38 |
$this->parser = $parser; |
35 |
- $this->unaryOperators = $unaryOperators; |
|
36 |
- $this->binaryOperators = $binaryOperators; |
|
39 |
+ |
|
40 |
+ if ($env instanceof Twig_Environment) { |
|
41 |
+ $this->env = $env; |
|
42 |
+ $this->unaryOperators = $env->getUnaryOperators(); |
|
43 |
+ $this->binaryOperators = $env->getBinaryOperators(); |
|
44 |
+ } else { |
|
45 |
+ @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED); |
|
46 |
+ |
|
47 |
+ $this->env = $parser->getEnvironment(); |
|
48 |
+ $this->unaryOperators = func_get_arg(1); |
|
49 |
+ $this->binaryOperators = func_get_arg(2); |
|
50 |
+ } |
|
37 | 51 |
} |
38 | 52 |
|
39 | 53 |
public function parseExpression($precedence = 0) |
... | ... |
@@ -44,7 +58,11 @@ class Twig_ExpressionParser |
44 | 58 |
$op = $this->binaryOperators[$token->getValue()]; |
45 | 59 |
$this->parser->getStream()->next(); |
46 | 60 |
|
47 |
- if (isset($op['callable'])) { |
|
61 |
+ if ('is not' === $token->getValue()) { |
|
62 |
+ $expr = $this->parseNotTestExpression($expr); |
|
63 |
+ } elseif ('is' === $token->getValue()) { |
|
64 |
+ $expr = $this->parseTestExpression($expr); |
|
65 |
+ } elseif (isset($op['callable'])) { |
|
48 | 66 |
$expr = call_user_func($op['callable'], $this->parser, $expr); |
49 | 67 |
} else { |
50 | 68 |
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); |
... | ... |
@@ -567,13 +585,78 @@ class Twig_ExpressionParser |
567 | 585 |
return new Twig_Node($targets); |
568 | 586 |
} |
569 | 587 |
|
570 |
- protected function getFunctionNodeClass($name, $line) |
|
588 |
+ private function parseNotTestExpression(Twig_NodeInterface $node) |
|
589 |
+ { |
|
590 |
+ return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); |
|
591 |
+ } |
|
592 |
+ |
|
593 |
+ private function parseTestExpression(Twig_NodeInterface $node) |
|
594 |
+ { |
|
595 |
+ $stream = $this->parser->getStream(); |
|
596 |
+ list($name, $test) = $this->getTest($node->getTemplateLine()); |
|
597 |
+ |
|
598 |
+ $class = $this->getTestNodeClass($test); |
|
599 |
+ $arguments = null; |
|
600 |
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|
601 |
+ $arguments = $this->parser->getExpressionParser()->parseArguments(true); |
|
602 |
+ } |
|
603 |
+ |
|
604 |
+ return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); |
|
605 |
+ } |
|
606 |
+ |
|
607 |
+ private function getTest($line) |
|
608 |
+ { |
|
609 |
+ $stream = $this->parser->getStream(); |
|
610 |
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); |
|
611 |
+ |
|
612 |
+ if ($test = $this->env->getTest($name)) { |
|
613 |
+ return array($name, $test); |
|
614 |
+ } |
|
615 |
+ |
|
616 |
+ if ($stream->test(Twig_Token::NAME_TYPE)) { |
|
617 |
+ // try 2-words tests |
|
618 |
+ $name = $name.' '.$this->parser->getCurrentToken()->getValue(); |
|
619 |
+ |
|
620 |
+ if ($test = $this->env->getTest($name)) { |
|
621 |
+ $stream->next(); |
|
622 |
+ |
|
623 |
+ return array($name, $test); |
|
624 |
+ } |
|
625 |
+ } |
|
626 |
+ |
|
627 |
+ $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName()); |
|
628 |
+ $e->addSuggestions($name, array_keys($this->env->getTests())); |
|
629 |
+ |
|
630 |
+ throw $e; |
|
631 |
+ } |
|
632 |
+ |
|
633 |
+ private function getTestNodeClass($test) |
|
571 | 634 |
{ |
572 |
- $env = $this->parser->getEnvironment(); |
|
635 |
+ if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { |
|
636 |
+ $message = sprintf('Twig Test "%s" is deprecated', $name); |
|
637 |
+ if (!is_bool($test->getDeprecatedVersion())) { |
|
638 |
+ $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); |
|
639 |
+ } |
|
640 |
+ if ($test->getAlternative()) { |
|
641 |
+ $message .= sprintf('. Use "%s" instead', $test->getAlternative()); |
|
642 |
+ } |
|
643 |
+ $message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine()); |
|
573 | 644 |
|
574 |
- if (false === $function = $env->getFunction($name)) { |
|
645 |
+ @trigger_error($message, E_USER_DEPRECATED); |
|
646 |
+ } |
|
647 |
+ |
|
648 |
+ if ($test instanceof Twig_SimpleTest) { |
|
649 |
+ return $test->getNodeClass(); |
|
650 |
+ } |
|
651 |
+ |
|
652 |
+ return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; |
|
653 |
+ } |
|
654 |
+ |
|
655 |
+ protected function getFunctionNodeClass($name, $line) |
|
656 |
+ { |
|
657 |
+ if (false === $function = $this->env->getFunction($name)) { |
|
575 | 658 |
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); |
576 |
- $e->addSuggestions($name, array_keys($env->getFunctions())); |
|
659 |
+ $e->addSuggestions($name, array_keys($this->env->getFunctions())); |
|
577 | 660 |
|
578 | 661 |
throw $e; |
579 | 662 |
} |
... | ... |
@@ -600,11 +683,9 @@ class Twig_ExpressionParser |
600 | 683 |
|
601 | 684 |
protected function getFilterNodeClass($name, $line) |
602 | 685 |
{ |
603 |
- $env = $this->parser->getEnvironment(); |
|
604 |
- |
|
605 |
- if (false === $filter = $env->getFilter($name)) { |
|
686 |
+ if (false === $filter = $this->env->getFilter($name)) { |
|
606 | 687 |
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); |
607 |
- $e->addSuggestions($name, array_keys($env->getFilters())); |
|
688 |
+ $e->addSuggestions($name, array_keys($this->env->getFilters())); |
|
608 | 689 |
|
609 | 690 |
throw $e; |
610 | 691 |
} |
... | ... |
@@ -258,82 +258,14 @@ class Twig_Extension_Core extends Twig_Extension |
258 | 258 |
'/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
259 | 259 |
'//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
260 | 260 |
'%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
261 |
- 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|
262 |
- 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|
261 |
+ 'is' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|
262 |
+ 'is not' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|
263 | 263 |
'**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), |
264 | 264 |
'??' => array('precedence' => 300, 'class' => 'Twig_Node_Expression_NullCoalesce', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), |
265 | 265 |
), |
266 | 266 |
); |
267 | 267 |
} |
268 | 268 |
|
269 |
- public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) |
|
270 |
- { |
|
271 |
- return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); |
|
272 |
- } |
|
273 |
- |
|
274 |
- public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) |
|
275 |
- { |
|
276 |
- $stream = $parser->getStream(); |
|
277 |
- list($name, $test) = $this->getTest($parser, $node->getTemplateLine()); |
|
278 |
- |
|
279 |
- if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { |
|
280 |
- $message = sprintf('Twig Test "%s" is deprecated', $name); |
|
281 |
- if (!is_bool($test->getDeprecatedVersion())) { |
|
282 |
- $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); |
|
283 |
- } |
|
284 |
- if ($test->getAlternative()) { |
|
285 |
- $message .= sprintf('. Use "%s" instead', $test->getAlternative()); |
|
286 |
- } |
|
287 |
- $message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine()); |
|
288 |
- |
|
289 |
- @trigger_error($message, E_USER_DEPRECATED); |
|
290 |
- } |
|
291 |
- |
|
292 |
- $class = $this->getTestNodeClass($parser, $test); |
|
293 |
- $arguments = null; |
|
294 |
- if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|
295 |
- $arguments = $parser->getExpressionParser()->parseArguments(true); |
|
296 |
- } |
|
297 |
- |
|
298 |
- return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine()); |
|
299 |
- } |
|
300 |
- |
|
301 |
- protected function getTest(Twig_Parser $parser, $line) |
|
302 |
- { |
|
303 |
- $stream = $parser->getStream(); |
|
304 |
- $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); |
|
305 |
- $env = $parser->getEnvironment(); |
|
306 |
- |
|
307 |
- if ($test = $env->getTest($name)) { |
|
308 |
- return array($name, $test); |
|
309 |
- } |
|
310 |
- |
|
311 |
- if ($stream->test(Twig_Token::NAME_TYPE)) { |
|
312 |
- // try 2-words tests |
|
313 |
- $name = $name.' '.$parser->getCurrentToken()->getValue(); |
|
314 |
- |
|
315 |
- if ($test = $env->getTest($name)) { |
|
316 |
- $parser->getStream()->next(); |
|
317 |
- |
|
318 |
- return array($name, $test); |
|
319 |
- } |
|
320 |
- } |
|
321 |
- |
|
322 |
- $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName()); |
|
323 |
- $e->addSuggestions($name, array_keys($env->getTests())); |
|
324 |
- |
|
325 |
- throw $e; |
|
326 |
- } |
|
327 |
- |
|
328 |
- protected function getTestNodeClass(Twig_Parser $parser, $test) |
|
329 |
- { |
|
330 |
- if ($test instanceof Twig_SimpleTest) { |
|
331 |
- return $test->getNodeClass(); |
|
332 |
- } |
|
333 |
- |
|
334 |
- return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; |
|
335 |
- } |
|
336 |
- |
|
337 | 269 |
public function getName() |
338 | 270 |
{ |
339 | 271 |
return 'core'; |
... | ... |
@@ -42,8 +42,13 @@ class Twig_Parser implements Twig_ParserInterface |
42 | 42 |
$this->env = $env; |
43 | 43 |
} |
44 | 44 |
|
45 |
+ /** |
|
46 |
+ * @deprecated since 1.27 (to be removed in 2.0) |
|
47 |
+ */ |
|
45 | 48 |
public function getEnvironment() |
46 | 49 |
{ |
50 |
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); |
|
51 |
+ |
|
47 | 52 |
return $this->env; |
48 | 53 |
} |
49 | 54 |
|
... | ... |
@@ -90,7 +95,7 @@ class Twig_Parser implements Twig_ParserInterface |
90 | 95 |
} |
91 | 96 |
|
92 | 97 |
if (null === $this->expressionParser) { |
93 |
- $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); |
|
98 |
+ $this->expressionParser = new Twig_ExpressionParser($this, $this->env); |
|
94 | 99 |
} |
95 | 100 |
|
96 | 101 |
$this->stream = $stream; |