optimized variable access when using PHP 5.4
authorFabien Potencier <fabien.potencier@gmail.com>
Fri, 18 Nov 2011 20:54:44 +0000 (21:54 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Fri, 18 Nov 2011 21:31:28 +0000 (22:31 +0100)
CHANGELOG
lib/Twig/Node/Expression/Name.php
lib/Twig/NodeVisitor/Optimizer.php
test/Twig/Tests/Node/Expression/GetAttrTest.php
test/Twig/Tests/Node/Expression/NameTest.php
test/Twig/Tests/Node/ForTest.php
test/Twig/Tests/Node/IfTest.php
test/Twig/Tests/Node/SetTest.php
test/Twig/Tests/Node/TestCase.php
test/Twig/Tests/NodeVisitor/OptimizerTest.php

index da1e22a..6cdb593 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 * 1.4.0
 
+ * optimized variable access when using PHP 5.4
  * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby
  * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders
  * added Twig_Function_Node to allow more complex functions to have their own Node class
index a8d206f..4b8d541 100644 (file)
@@ -35,18 +35,29 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression
         } elseif ($this->isSpecial()) {
             $compiler->raw($this->specialVars[$name]);
         } else {
-            $compiler
-                ->raw('$this->getContext($context, ')
-                ->string($name)
-            ;
+            if (version_compare(phpversion(), '5.4.0RC1', '>=') && ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables())) {
+                // PHP 5.4 ternary operator performance was optimized
+                $compiler
+                    ->raw('(isset($context[')
+                    ->string($name)
+                    ->raw(']) ? $context[')
+                    ->string($name)
+                    ->raw('] : null)')
+                ;
+            } else {
+                $compiler
+                    ->raw('$this->getContext($context, ')
+                    ->string($name)
+                ;
 
-            if ($this->getAttribute('ignore_strict_check')) {
-                $compiler->raw(', true');
-            }
+                if ($this->getAttribute('ignore_strict_check')) {
+                    $compiler->raw(', true');
+                }
 
-            $compiler
-                ->raw(')')
-            ;
+                $compiler
+                    ->raw(')')
+                ;
+            }
         }
     }
 
index 8a7813a..cbc61fc 100644 (file)
@@ -56,7 +56,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
             $this->enterOptimizeFor($node, $env);
         }
 
-        if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
+        if (!version_compare(phpversion(), '5.4.0RC1', '>=')  && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
             if ($this->inABody) {
                 if (!$node instanceof Twig_Node_Expression) {
                     if (get_class($node) !== 'Twig_Node') {
index e3c2eaa..264618d 100644 (file)
@@ -49,10 +49,10 @@ class Twig_Tests_Node_Expression_GetAttrTest extends Twig_Tests_Node_TestCase
         $attr = new Twig_Node_Expression_Constant('bar', 0);
         $args = new Twig_Node();
         $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::ANY_CALL, 0);
-        $tests[] = array($node, '$this->getAttribute($this->getContext($context, "foo"), "bar")');
+        $tests[] = array($node, sprintf('$this->getAttribute(%s, "bar")', $this->getVariableGetter('foo')));
 
         $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::ARRAY_CALL, 0);
-        $tests[] = array($node, '$this->getAttribute($this->getContext($context, "foo"), "bar", array(), "array")');
+        $tests[] = array($node, sprintf('$this->getAttribute(%s, "bar", array(), "array")', $this->getVariableGetter('foo')));
 
 
         $args = new Twig_Node(array(
@@ -60,7 +60,7 @@ class Twig_Tests_Node_Expression_GetAttrTest extends Twig_Tests_Node_TestCase
             new Twig_Node_Expression_Constant('bar', 0),
         ));
         $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::METHOD_CALL, 0);
-        $tests[] = array($node, '$this->getAttribute($this->getContext($context, "foo"), "bar", array($this->getContext($context, "foo"), "bar", ), "method")');
+        $tests[] = array($node, sprintf('$this->getAttribute(%s, "bar", array(%s, "bar", ), "method")', $this->getVariableGetter('foo'), $this->getVariableGetter('foo')));
 
         return $tests;
     }
index f041310..e77b432 100644 (file)
@@ -39,10 +39,11 @@ class Twig_Tests_Node_Expression_NameTest extends Twig_Tests_Node_TestCase
         $context = new Twig_Node_Expression_Name('_context', 0);
 
         $env = new Twig_Environment(null, array('strict_variables' => true));
+        $env1 = new Twig_Environment(null, array('strict_variables' => false));
 
         return array(
             array($node, '$this->getContext($context, "foo")', $env),
-            array($node, '$this->getContext($context, "foo")'),
+            array($node, $this->getVariableGetter('foo'), $env1),
             array($self, '$this'),
             array($context, '$context'),
         );
index 68a3daa..1a9a418 100644 (file)
@@ -65,9 +65,9 @@ class Twig_Tests_Node_ForTest extends Twig_Tests_Node_TestCase
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_ensure_traversable(\$this->getContext(\$context, "items"));
+\$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('items')});
 foreach (\$context['_seq'] as \$context["key"] => \$context["item"]) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
 }
 \$_parent = \$context['_parent'];
 unset(\$context['_seq'], \$context['_iterated'], \$context['key'], \$context['item'], \$context['_parent'], \$context['loop']);
@@ -86,7 +86,7 @@ EOF
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_ensure_traversable(\$this->getContext(\$context, "values"));
+\$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
 \$context['loop'] = array(
   'parent' => \$context['_parent'],
   'index0' => 0,
@@ -101,7 +101,7 @@ if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_
     \$context['loop']['last'] = 1 === \$length;
 }
 foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
     ++\$context['loop']['index0'];
     ++\$context['loop']['index'];
     \$context['loop']['first'] = false;
@@ -128,7 +128,7 @@ EOF
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_ensure_traversable(\$this->getContext(\$context, "values"));
+\$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
 \$context['loop'] = array(
   'parent' => \$context['_parent'],
   'index0' => 0,
@@ -137,7 +137,7 @@ EOF
 );
 foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
     if (true) {
-        echo \$this->getContext(\$context, "foo");
+        echo {$this->getVariableGetter('foo')};
         ++\$context['loop']['index0'];
         ++\$context['loop']['index'];
         \$context['loop']['first'] = false;
@@ -160,7 +160,7 @@ EOF
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_ensure_traversable(\$this->getContext(\$context, "values"));
+\$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
 \$context['_iterated'] = false;
 \$context['loop'] = array(
   'parent' => \$context['_parent'],
@@ -176,7 +176,7 @@ if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_
     \$context['loop']['last'] = 1 === \$length;
 }
 foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
     \$context['_iterated'] = true;
     ++\$context['loop']['index0'];
     ++\$context['loop']['index'];
@@ -188,7 +188,7 @@ foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
     }
 }
 if (!\$context['_iterated']) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
 }
 \$_parent = \$context['_parent'];
 unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
index e6995c8..04b92e7 100644 (file)
@@ -55,7 +55,7 @@ class Twig_Tests_Node_IfTest extends Twig_Tests_Node_TestCase
 
         $tests[] = array($node, <<<EOF
 if (true) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
 }
 EOF
         );
@@ -71,9 +71,9 @@ EOF
 
         $tests[] = array($node, <<<EOF
 if (true) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
 } elseif (false) {
-    echo \$this->getContext(\$context, "bar");
+    echo {$this->getVariableGetter('bar')};
 }
 EOF
         );
@@ -87,9 +87,9 @@ EOF
 
         $tests[] = array($node, <<<EOF
 if (true) {
-    echo \$this->getContext(\$context, "foo");
+    echo {$this->getVariableGetter('foo')};
 } else {
-    echo \$this->getContext(\$context, "bar");
+    echo {$this->getVariableGetter('bar')};
 }
 EOF
         );
index d7e0d5f..d40b90d 100644 (file)
@@ -64,7 +64,7 @@ EOF
         $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 0), new Twig_Node_Expression_Name('bar', 0)), array(), 0);
         $node = new Twig_Node_Set(false, $names, $values, 0);
         $tests[] = array($node, <<<EOF
-list(\$context["foo"], \$context["bar"]) = array("foo", \$this->getContext(\$context, "bar"));
+list(\$context["foo"], \$context["bar"]) = array("foo", {$this->getVariableGetter('bar')});
 EOF
         );
 
index c5b974f..ec4eee4 100644 (file)
@@ -37,4 +37,13 @@ abstract class Twig_Tests_Node_TestCase extends PHPUnit_Framework_TestCase
     {
         return new Twig_Environment();
     }
+
+    protected function getVariableGetter($name)
+    {
+        if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
+            return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name);
+        }
+
+        return sprintf('$this->getContext($context, "%s")', $name);
+    }
 }
index edf6c51..94a4cfa 100644 (file)
@@ -38,6 +38,10 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
 
     public function testRenderVariableBlockOptimizer()
     {
+        if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
+            return;
+        }
+
         $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
         $env->addExtension(new Twig_Extension_Optimizer());
         $stream = $env->parse($env->tokenize('{{ block(name|lower) }}', 'index'));