some improvements to for tag compiler
authornikic <+@ni-po.com>
Thu, 16 Dec 2010 22:30:30 +0000 (23:30 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Sat, 18 Dec 2010 08:04:23 +0000 (09:04 +0100)
doc/templates.rst
lib/Twig/Extension/Core.php
lib/Twig/Node/For.php
test/Twig/Tests/Node/ForTest.php

index 02bbdcc..47df16b 100644 (file)
@@ -482,7 +482,7 @@ provided in a variable called ``users``:
 .. note::
 
     A sequence can be either an array or an object implementing the
-    ``Iterator`` interface.
+    ``Traversable`` interface.
 
 If you do need to iterate over a sequence of numbers, you can use the ``..``
 operator (as of Twig 0.9.5):
index 506e03d..6bb7192 100644 (file)
@@ -428,11 +428,9 @@ else
     }
 }
 
-function twig_iterator_to_array($seq, $useKeys = true)
+function twig_ensure_traversable($seq)
 {
-    if (is_array($seq)) {
-        return $seq;
-    } elseif (is_object($seq) && $seq instanceof Traversable) {
+    if (is_array($seq) || (is_object($seq) && $seq instanceof Traversable)) {
         return $seq;
     } else {
         return array();
index 4580c2f..ec48f28 100644 (file)
@@ -33,32 +33,27 @@ class Twig_Node_For extends Twig_Node
         $compiler
             ->addDebugInfo($this)
             // the (array) cast bypasses a PHP 5.2.6 bug
-            ->write('$context[\'_parent\'] = (array) $context;'."\n")
+            ->write("\$context['_parent'] = (array) \$context;\n")
+            ->write("\$context['_seq'] = twig_ensure_traversable(")
+            ->subcompile($this->getNode('seq'))
+            ->raw(");\n")
         ;
 
-        if (null !== $this->getNode('else')) {
+        if (null !== $this->getNode('else') || null !== $this->getNode('joined_with')) {
             $compiler->write("\$context['_iterated'] = false;\n");
         }
 
-        $compiler
-            ->write("\$context['_seq'] = twig_iterator_to_array(")
-            ->subcompile($this->getNode('seq'))
-            ->raw(");\n")
-        ;
-
         if ($this->getAttribute('with_loop')) {
             $compiler
-                ->write("\$countable = is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable);\n")
-                ->write("\$length = \$countable ? count(\$context['_seq']) : null;\n")
-
                 ->write("\$context['loop'] = array(\n")
                 ->write("  'parent' => \$context['_parent'],\n")
                 ->write("  'index0' => 0,\n")
                 ->write("  'index'  => 1,\n")
                 ->write("  'first'  => true,\n")
                 ->write(");\n")
-                ->write("if (\$countable) {\n")
+                ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n")
                 ->indent()
+                ->write("\$length = count(\$context['_seq']);\n")
                 ->write("\$context['loop']['revindex0'] = \$length - 1;\n")
                 ->write("\$context['loop']['revindex'] = \$length;\n")
                 ->write("\$context['loop']['length'] = \$length;\n")
@@ -68,10 +63,6 @@ class Twig_Node_For extends Twig_Node
             ;
         }
 
-        if (null !== $this->getNode('joined_with')) {
-            $compiler->write("\$context['_first_iteration'] = true;\n");
-        }
-
         $compiler
             ->write("foreach (\$context['_seq'] as ")
             ->subcompile($this->getNode('key_target'))
@@ -81,17 +72,9 @@ class Twig_Node_For extends Twig_Node
             ->indent()
         ;
 
-        if (null !== $this->getNode('else')) {
-            $compiler->write("\$context['_iterated'] = true;\n");
-        }
-
         if (null !== $this->getNode('joined_with')) {
             $compiler
-                ->write("if (\$context['_first_iteration']) {\n")
-                ->indent()
-                ->write("\$context['_first_iteration'] = false;\n")
-                ->outdent()
-                ->write("} else {\n")
+                ->write("if (\$context['_iterated']) {\n")
                 ->indent()
                 ->write("echo ")
                 ->subcompile($this->getNode('joined_with'))
@@ -102,12 +85,16 @@ class Twig_Node_For extends Twig_Node
 
         $compiler->subcompile($this->getNode('body'));
 
+        if (null !== $this->getNode('else') || null !== $this->getNode('joined_with')) {
+            $compiler->write("\$context['_iterated'] = true;\n");
+        }
+
         if ($this->getAttribute('with_loop')) {
             $compiler
                 ->write("++\$context['loop']['index0'];\n")
                 ->write("++\$context['loop']['index'];\n")
                 ->write("\$context['loop']['first'] = false;\n")
-                ->write("if (\$countable) {\n")
+                ->write("if (isset(\$context['loop']['length'])) {\n")
                 ->indent()
                 ->write("--\$context['loop']['revindex0'];\n")
                 ->write("--\$context['loop']['revindex'];\n")
@@ -132,12 +119,12 @@ class Twig_Node_For extends Twig_Node
             ;
         }
 
-        $compiler->write('$_parent = $context[\'_parent\'];'."\n");
+        $compiler->write("\$_parent = \$context['_parent'];\n");
 
         // remove some "private" loop variables (needed for nested loops)
         $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
 
-        /// keep the values set in the inner context for variables defined in the outer context
-        $compiler->write('$context = array_merge($_parent, array_intersect_key($context, $_parent));'."\n");
+        // keep the values set in the inner context for variables defined in the outer context
+        $compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n");
     }
 }
index 06fe129..dc1a785 100644 (file)
@@ -61,7 +61,7 @@ class Twig_Tests_Node_ForTest extends Twig_Tests_Node_TestCase
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_iterator_to_array((isset(\$context['items']) ? \$context['items'] : null));
+\$context['_seq'] = twig_ensure_traversable((isset(\$context['items']) ? \$context['items'] : null));
 foreach (\$context['_seq'] as \$context['key'] => \$context['item']) {
     echo (isset(\$context['foo']) ? \$context['foo'] : null);
 }
@@ -82,33 +82,31 @@ EOF
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
-\$context['_seq'] = twig_iterator_to_array((isset(\$context['values']) ? \$context['values'] : null));
-\$countable = is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable);
-\$length = \$countable ? count(\$context['_seq']) : null;
+\$context['_seq'] = twig_ensure_traversable((isset(\$context['values']) ? \$context['values'] : null));
+\$context['_iterated'] = false;
 \$context['loop'] = array(
   'parent' => \$context['_parent'],
   'index0' => 0,
   'index'  => 1,
   'first'  => true,
 );
-if (\$countable) {
+if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {
+    \$length = count(\$context['_seq']);
     \$context['loop']['revindex0'] = \$length - 1;
     \$context['loop']['revindex'] = \$length;
     \$context['loop']['length'] = \$length;
     \$context['loop']['last'] = 1 === \$length;
 }
-\$context['_first_iteration'] = true;
 foreach (\$context['_seq'] as \$context['k'] => \$context['v']) {
-    if (\$context['_first_iteration']) {
-        \$context['_first_iteration'] = false;
-    } else {
+    if (\$context['_iterated']) {
         echo ", ";
     }
     echo (isset(\$context['foo']) ? \$context['foo'] : null);
+    \$context['_iterated'] = true;
     ++\$context['loop']['index0'];
     ++\$context['loop']['index'];
     \$context['loop']['first'] = false;
-    if (\$countable) {
+    if (isset(\$context['loop']['length'])) {
         --\$context['loop']['revindex0'];
         --\$context['loop']['revindex'];
         \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];
@@ -130,29 +128,28 @@ EOF
 
         $tests[] = array($node, <<<EOF
 \$context['_parent'] = (array) \$context;
+\$context['_seq'] = twig_ensure_traversable((isset(\$context['values']) ? \$context['values'] : null));
 \$context['_iterated'] = false;
-\$context['_seq'] = twig_iterator_to_array((isset(\$context['values']) ? \$context['values'] : null));
-\$countable = is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable);
-\$length = \$countable ? count(\$context['_seq']) : null;
 \$context['loop'] = array(
   'parent' => \$context['_parent'],
   'index0' => 0,
   'index'  => 1,
   'first'  => true,
 );
-if (\$countable) {
+if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {
+    \$length = count(\$context['_seq']);
     \$context['loop']['revindex0'] = \$length - 1;
     \$context['loop']['revindex'] = \$length;
     \$context['loop']['length'] = \$length;
     \$context['loop']['last'] = 1 === \$length;
 }
 foreach (\$context['_seq'] as \$context['k'] => \$context['v']) {
-    \$context['_iterated'] = true;
     echo (isset(\$context['foo']) ? \$context['foo'] : null);
+    \$context['_iterated'] = true;
     ++\$context['loop']['index0'];
     ++\$context['loop']['index'];
     \$context['loop']['first'] = false;
-    if (\$countable) {
+    if (isset(\$context['loop']['length'])) {
         --\$context['loop']['revindex0'];
         --\$context['loop']['revindex'];
         \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];