Browse code

feature #2206 Move tests handling in Twig_ParserExpression instead of Twig_Extension_Core (fabpot)

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

Fabien Potencier authored on 24/10/2016 23:48:00
Showing 5 changed files
... ...
@@ -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;