feature #2909 Allow Traversable objects to be used in the with keyword of the include and embed tags (thewilkybarkid)

This PR was squashed before being merged into the 1.x branch (closes #2909).

Discussion
----------

Allow Traversable objects to be used in the with keyword of the include and embed tags

Same as #2901 but for `include` and `embed`.

Commits
-------

068306fe Allow Traversable objects to be used in the with keyword of the include and embed tags
This commit is contained in:
Fabien Potencier
2019-03-21 15:27:58 +01:00
6 changed files with 56 additions and 42 deletions
+1
View File
@@ -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)
+36 -31
View File
@@ -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;
+3 -1
View File
@@ -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(')');
}
}
}
@@ -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,
@@ -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
+3 -3
View File
@@ -62,14 +62,14 @@ EOF
$node = new IncludeNode($expr, $vars, false, false, 1);
$tests[] = [$node, <<<EOF
// line 1
\$this->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, <<<EOF
// line 1
\$this->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, <<<EOF
// line 1
try {
\$this->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
}