diff --git a/CHANGELOG b/CHANGELOG index 29cb43fca..e94890ae3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ * fixed BC break on Environment::resolveTemplate() * fixed the bundled Autoloader to also load namespaced classes * allowed Traversable objects to be used in the "with" tag + * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags * 1.38.2 (2019-03-12) diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 73e83782f..c959e315b 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -362,9 +362,7 @@ function twig_random(Environment $env, $values = null, $max = null) return mt_rand($min, $max); } - if ($values instanceof \Traversable) { - $values = iterator_to_array($values); - } elseif (\is_string($values)) { + if (\is_string($values)) { if ('' === $values) { return ''; } @@ -387,10 +385,12 @@ function twig_random(Environment $env, $values = null, $max = null) } } - if (!\is_array($values)) { + if (!twig_test_iterable($values)) { return $values; } + $values = twig_to_array($values); + if (0 === \count($values)) { throw new RuntimeError('The random function cannot pick from an empty array.'); } @@ -510,17 +510,15 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) */ function twig_replace_filter($str, $from, $to = null) { - if ($from instanceof \Traversable) { - $from = iterator_to_array($from); - } elseif (\is_string($from) && \is_string($to)) { + if (\is_string($from) && \is_string($to)) { @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); return strtr($str, $from, $to); - } elseif (!\is_array($from)) { + } elseif (!twig_test_iterable($from)) { throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); } - return strtr($str, $from); + return strtr($str, twig_to_array($from)); } /** @@ -639,19 +637,15 @@ function _twig_markup2string(&$value) */ function twig_array_merge($arr1, $arr2) { - if ($arr1 instanceof \Traversable) { - $arr1 = iterator_to_array($arr1); - } elseif (!\is_array($arr1)) { + if (!twig_test_iterable($arr1)) { throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); } - if ($arr2 instanceof \Traversable) { - $arr2 = iterator_to_array($arr2); - } elseif (!\is_array($arr2)) { + if (!twig_test_iterable($arr2)) { throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); } - return array_merge($arr1, $arr2); + return array_merge(twig_to_array($arr1), twig_to_array($arr2)); } /** @@ -745,13 +739,9 @@ function twig_last(Environment $env, $item) */ function twig_join_filter($value, $glue = '', $and = null) { - if ($value instanceof \Traversable) { - $value = iterator_to_array($value, false); - } else { - $value = (array) $value; - } + $value = twig_to_array($value, false); - if (0 === \count($value)) { + if (!\is_array($value) || 0 === \count($value)) { return ''; } @@ -759,12 +749,11 @@ function twig_join_filter($value, $glue = '', $and = null) return implode($glue, $value); } - $v = array_values($value); - if (1 === \count($v)) { - return $v[0]; + if (1 === \count($value)) { + return $value[0]; } - return implode($glue, \array_slice($value, 0, -1)).$and.$v[\count($v) - 1]; + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; } /** @@ -1470,6 +1459,26 @@ function twig_ensure_traversable($seq) return []; } +/** + * @internal + */ +function twig_to_array($seq, $preserveKeys = true) +{ + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!is_array($seq)) { + return (array) $seq; + } + + if(!$preserveKeys) { + return array_values($seq); + } + + return $seq; +} + /** * Checks if a variable is empty. * @@ -1640,13 +1649,9 @@ function twig_constant_is_defined($constant, $object = null) */ function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) { - if ($items instanceof \Traversable) { - $items = iterator_to_array($items, $preserveKeys); - } - $size = ceil($size); - $result = array_chunk($items, $size, $preserveKeys); + $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); if (null !== $fill && $result) { $last = \count($result) - 1; diff --git a/src/Node/IncludeNode.php b/src/Node/IncludeNode.php index 524ddef23..72b924771 100644 --- a/src/Node/IncludeNode.php +++ b/src/Node/IncludeNode.php @@ -82,12 +82,14 @@ class IncludeNode extends Node implements NodeOutputInterface $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); } elseif (false === $this->getAttribute('only')) { $compiler - ->raw('array_merge($context, ') + ->raw('twig_array_merge($context, ') ->subcompile($this->getNode('variables')) ->raw(')') ; } else { + $compiler->raw('twig_to_array('); $compiler->subcompile($this->getNode('variables')); + $compiler->raw(')'); } } } diff --git a/test/Twig/Tests/Fixtures/tags/include/only.test b/test/Twig/Tests/Fixtures/tags/include/only.test index d595f8061..8da402f7a 100644 --- a/test/Twig/Tests/Fixtures/tags/include/only.test +++ b/test/Twig/Tests/Fixtures/tags/include/only.test @@ -3,14 +3,18 @@ --TEMPLATE-- {% include "foo.twig" %} {% include "foo.twig" only %} -{% include "foo.twig" with {'foo1': 'bar'} %} -{% include "foo.twig" with {'foo1': 'bar'} only %} +{% include "foo.twig" with vars1 %} +{% include "foo.twig" with vars1 only %} +{% include "foo.twig" with vars2 %} +{% include "foo.twig" with vars2 only %} --TEMPLATE(foo.twig)-- {% for k, v in _context %}{{ k }},{% endfor %} --DATA-- -return ['foo' => 'bar'] +return ['vars1' => ['foo1' => 'bar'], 'vars2' => new ArrayObject(['foo2' => 'bar'])] --EXPECT-- -foo,global,_parent, +vars1,vars2,global,_parent, global,_parent, -foo,global,foo1,_parent, +vars1,vars2,global,foo1,_parent, foo1,global,_parent, +vars1,vars2,global,foo2,_parent, +foo2,global,_parent, diff --git a/test/Twig/Tests/Fixtures/tags/include/with_variables.test b/test/Twig/Tests/Fixtures/tags/include/with_variables.test index 3b192aeb2..45a05199e 100644 --- a/test/Twig/Tests/Fixtures/tags/include/with_variables.test +++ b/test/Twig/Tests/Fixtures/tags/include/with_variables.test @@ -2,11 +2,13 @@ "include" tag accept variables --TEMPLATE-- {% include "foo.twig" with {'foo': 'bar'} %} -{% include "foo.twig" with vars %} +{% include "foo.twig" with vars1 %} +{% include "foo.twig" with vars2 %} --TEMPLATE(foo.twig)-- {{ foo }} --DATA-- -return ['vars' => ['foo' => 'bar']] +return ['vars1' => ['foo' => 'bar'], 'vars2' => new ArrayObject(['foo' => 'bar'])] --EXPECT-- bar bar +bar diff --git a/test/Twig/Tests/Node/IncludeTest.php b/test/Twig/Tests/Node/IncludeTest.php index bdb9d0a99..d24336e30 100644 --- a/test/Twig/Tests/Node/IncludeTest.php +++ b/test/Twig/Tests/Node/IncludeTest.php @@ -62,14 +62,14 @@ EOF $node = new IncludeNode($expr, $vars, false, false, 1); $tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->display(array_merge(\$context, ["foo" => true])); +\$this->loadTemplate("foo.twig", null, 1)->display(twig_array_merge(\$context, ["foo" => true])); EOF ]; $node = new IncludeNode($expr, $vars, true, false, 1); $tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->display(["foo" => true]); +\$this->loadTemplate("foo.twig", null, 1)->display(twig_to_array(["foo" => true])); EOF ]; @@ -77,7 +77,7 @@ EOF $tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->display(["foo" => true]); + \$this->loadTemplate("foo.twig", null, 1)->display(twig_to_array(["foo" => true])); } catch (LoaderError \$e) { // ignore missing template }