From dfa0cb91dd4f0e369bc4825f06940ba16990a467 Mon Sep 17 00:00:00 2001 From: nikic <+@ni-po.com> Date: Thu, 16 Dec 2010 23:30:30 +0100 Subject: [PATCH] some improvements to for tag compiler --- doc/templates.rst | 2 +- lib/Twig/Extension/Core.php | 6 +--- lib/Twig/Node/For.php | 45 +++++++++++++------------------------ test/Twig/Tests/Node/ForTest.php | 29 +++++++++++------------- 4 files changed, 32 insertions(+), 50 deletions(-) diff --git a/doc/templates.rst b/doc/templates.rst index 02bbdcc..47df16b 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -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): diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index 506e03d..6bb7192 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -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(); diff --git a/lib/Twig/Node/For.php b/lib/Twig/Node/For.php index 4580c2f..ec48f28 100644 --- a/lib/Twig/Node/For.php +++ b/lib/Twig/Node/For.php @@ -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"); } } diff --git a/test/Twig/Tests/Node/ForTest.php b/test/Twig/Tests/Node/ForTest.php index 06fe129..dc1a785 100644 --- a/test/Twig/Tests/Node/ForTest.php +++ b/test/Twig/Tests/Node/ForTest.php @@ -61,7 +61,7 @@ class Twig_Tests_Node_ForTest extends Twig_Tests_Node_TestCase $tests[] = array($node, << \$context['item']) { echo (isset(\$context['foo']) ? \$context['foo'] : null); } @@ -82,33 +82,31 @@ EOF $tests[] = array($node, << \$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, << \$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']; -- 1.7.2.5