Browse code

feature #2250 Add support for PHP 7 null coalescing operator (fabpot)

This PR was merged into the 1.x branch.

Discussion
----------

Add support for PHP 7 null coalescing operator

This is just one case where we can "optimize" the ternary operator. PHP 7 also supports more complex expressions that can return `null`. The rule here seems to be that you cannot have more than one level of undefinedness.

So, for instance, if `log` is defined, `log("foo") ?? "NOPE"` works, but `log($a["foo"]) ?? "NOPE"` does not if `$a["foo"]` is not defined. If we want to convert the code to always use `??` when possible, this should probably be implemented in the Optimizer node visitor, not here. But the question is: is it worth it in terms of performance? It cleans up the generated code quite a bit though.

closes #1979

Commits
-------

cfc3931 added support for PHP 7 null coalescing operator

Fabien Potencier authored on 17/11/2016 14:17:22
Showing 3 changed files
... ...
@@ -1,5 +1,6 @@
1 1
 * 1.28.0 (2016-XX-XX)
2 2
 
3
+ * added support for the PHP 7 null coalescing operator for the ?? Twig implementation
3 4
  * exposed a way to access template data and methods in a portable way
4 5
  * changed context access to use the PHP 7 null coalescing operator when available
5 6
  * added the "with" tag
... ...
@@ -20,4 +20,27 @@ class Twig_Node_Expression_NullCoalesce extends Twig_Node_Expression_Conditional
20 20
 
21 21
         parent::__construct($test, $left, $right, $lineno);
22 22
     }
23
+
24
+    public function compile(Twig_Compiler $compiler)
25
+    {
26
+        /*
27
+         * This optimizes only one case. PHP 7 also supports more complex expressions
28
+         * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works,
29
+         * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced
30
+         * cases might be implemented as an optimizer node visitor, but has not been done
31
+         * as benefits are probably not worth the added complexity.
32
+         */
33
+        if (PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof Twig_Node_Expression_Name) {
34
+            $this->getNode('expr2')->setAttribute('always_defined', true);
35
+            $compiler
36
+                ->raw('((')
37
+                ->subcompile($this->getNode('expr2'))
38
+                ->raw(') ?? (')
39
+                ->subcompile($this->getNode('expr3'))
40
+                ->raw('))')
41
+            ;
42
+        } else {
43
+            parent::compile($compiler);
44
+        }
45
+    }
23 46
 }
24 47
new file mode 100644
... ...
@@ -0,0 +1,31 @@
1
+<?php
2
+
3
+/*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+class Twig_Tests_Node_Expression_NullCoalesceTest extends Twig_Test_NodeTestCase
13
+{
14
+    public function getTests()
15
+    {
16
+        $tests = array();
17
+
18
+        $left = new Twig_Node_Expression_Name('foo', 1);
19
+        $right = new Twig_Node_Expression_Constant(2, 1);
20
+        $node = new Twig_Node_Expression_NullCoalesce($left, $right, 1);
21
+        if (PHP_VERSION_ID >= 70000) {
22
+            $tests[] = array($node, "((// line 1\n\$context[\"foo\"]) ?? (2))");
23
+        } elseif (PHP_VERSION_ID >= 50400) {
24
+            $tests[] = array($node, "(((// line 1\narray_key_exists(\"foo\", \$context) &&  !(null === (isset(\$context[\"foo\"]) ? \$context[\"foo\"] : null)))) ? ((isset(\$context[\"foo\"]) ? \$context[\"foo\"] : null)) : (2))");
25
+        } else {
26
+            $tests[] = array($node, "(((// line 1\narray_key_exists(\"foo\", \$context) &&  !(null === \$this->getContext(\$context, \"foo\")))) ? (\$this->getContext(\$context, \"foo\")) : (2))");
27
+        }
28
+
29
+        return $tests;
30
+    }
31
+}