Skip to content

Commit

Permalink
feature #1795 Traversable support for 'replace', 'merge' and 'sort' (…
Browse files Browse the repository at this point in the history
…SpacePossum)

This PR was merged into the 1.x branch.

Discussion
----------

Traversable support for 'replace', 'merge' and 'sort'

Add traversable support to:
* replace
* merge
* sort

Other changes:
* Removes some not need checks
* ~~Make unit test skip if it cannot write to cache because of the user running the test~~
* Tests added for traversable support
* Doc updates

Deprecating:
I think the undocumented (and multibyte not supported) use of `replace(string, string)` can be removed in Twig 2.0.
Adding MB support and documentation of the feature was turned down (#1618)
However removing it is a BC break (#1445 (comment))

Commits
-------

2a17303 Mark test skipped if cannot write to cache directory.
  • Loading branch information
fabpot committed Sep 6, 2015
2 parents b1a4c14 + 2a17303 commit 542dd3a
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 14 deletions.
3 changes: 2 additions & 1 deletion doc/filters/merge.rst
Expand Up @@ -42,6 +42,7 @@ overridden.
.. note::

Internally, Twig uses the PHP `array_merge`_ function.
Internally, Twig uses the PHP `array_merge`_ function. It supports
Traversable objects by transforming those to arrays.

.. _`array_merge`: http://php.net/array_merge
3 changes: 2 additions & 1 deletion doc/filters/sort.rst
Expand Up @@ -12,6 +12,7 @@ The ``sort`` filter sorts an array:
.. note::

Internally, Twig uses the PHP `asort`_ function to maintain index
association.
association. It supports Traversable objects by transforming
those to arrays.

.. _`asort`: http://php.net/asort
54 changes: 46 additions & 8 deletions lib/Twig/Extension/Core.php
Expand Up @@ -152,7 +152,7 @@ public function getFilters()
new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('format', 'sprintf'),
new Twig_SimpleFilter('replace', 'strtr'),
new Twig_SimpleFilter('replace', 'twig_replace_filter'),
new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('abs', 'abs'),
new Twig_SimpleFilter('round', 'twig_round'),
Expand Down Expand Up @@ -549,6 +549,30 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu
return $date;
}

/**
* Replaces strings within a string.
*
* @param string $str String to replace in
* @param array|Traversable $from Replace values
* @param string|null $to Replace to, deprecated (@see http://php.net/manual/en/function.strtr.php)
*
* @return string
*/
function twig_replace_filter($str, $from, $to = null)
{
if ($from instanceof Traversable) {
$from = iterator_to_array($from);
} elseif (is_string($from) && is_string($to)) {
@trigger_error('Using "replace" with character by character replacement is deprecated and will be removed in Twig 2.x', E_USER_DEPRECATED);

return strtr($str, $from, $to);
} elseif (!is_array($from)) {
throw new Twig_Error_Runtime(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);
}

/**
* Rounds a number.
*
Expand Down Expand Up @@ -682,15 +706,23 @@ function _twig_markup2string(&$value)
* {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
* </pre>
*
* @param array $arr1 An array
* @param array $arr2 An array
* @param array|Traversable $arr1 An array
* @param array|Traversable $arr2 An array
*
* @return array The merged array
*/
function twig_array_merge($arr1, $arr2)
{
if (!is_array($arr1) || !is_array($arr2)) {
throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or hashes; %s and %s given.', gettype($arr1), gettype($arr2)));
if ($arr1 instanceof Traversable) {
$arr1 = iterator_to_array($arr1);
} elseif (!is_array($arr1)) {
throw new Twig_Error_Runtime(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)) {
throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', gettype($arr2)));
}

return array_merge($arr1, $arr2);
Expand Down Expand Up @@ -874,7 +906,7 @@ function _twig_default_filter($value, $default = '')
*/
function twig_get_array_keys_filter($array)
{
if (is_object($array) && $array instanceof Traversable) {
if ($array instanceof Traversable) {
return array_keys(iterator_to_array($array));
}

Expand All @@ -896,7 +928,7 @@ function twig_get_array_keys_filter($array)
*/
function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
{
if (is_object($item) && $item instanceof Traversable) {
if ($item instanceof Traversable) {
return array_reverse(iterator_to_array($item), $preserveKeys);
}

Expand Down Expand Up @@ -928,12 +960,18 @@ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false
/**
* Sorts an array.
*
* @param array $array
* @param array|Traversable $array
*
* @return array
*/
function twig_sort_filter($array)
{
if ($array instanceof Traversable) {
$array = iterator_to_array($array);
} elseif (!is_array($array)) {
throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array)));
}

asort($array);

return $array;
Expand Down
4 changes: 3 additions & 1 deletion test/Twig/Tests/Fixtures/filters/merge.test
Expand Up @@ -6,11 +6,13 @@
{{ {'bar': 'foo'}|merge(items)|join }}
{{ {'bar': 'foo'}|merge(items)|keys|join }}
{{ numerics|merge([4, 5, 6])|join }}
{{ traversable.a|merge(traversable.b)|join }}
--DATA--
return array('items' => array('foo' => 'bar'), 'numerics' => array(1, 2, 3))
return array('items' => array('foo' => 'bar'), 'numerics' => array(1, 2, 3), 'traversable' => array('a' => new ArrayObject(array(0 => 1, 1 => 2, 2 => 3)), 'b' => new ArrayObject(array('a' => 'b'))))
--EXPECT--
barfoo
foobar
foobar
barfoo
123456
123b
8 changes: 6 additions & 2 deletions test/Twig/Tests/Fixtures/filters/replace.test
@@ -1,8 +1,12 @@
--TEST--
"replace" filter
--TEMPLATE--
{{ "I like %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }}
{{ "I liké %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }}
{{ 'I like single replace operation only %that%'|replace({'%that%' : '%that%1'}) }}
{{ 'I like %this% and %that%.'|replace(traversable) }}
--DATA--
return array()
return array('traversable' => new ArrayObject(array('%this%' => 'foo', '%that%' => 'bar')))
--EXPECT--
I liké foo and bar.
I like single replace operation only %that%1
I like foo and bar.
8 changes: 8 additions & 0 deletions test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test
@@ -0,0 +1,8 @@
--TEST--
Exception for invalid argument type in replace call
--TEMPLATE--
{{ 'test %foo%'|replace(stdClass) }}
--DATA--
return array('stdClass' => new \stdClass())
--EXCEPTION--
Twig_Error_Runtime: The "replace" filter expects an array or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2.
4 changes: 3 additions & 1 deletion test/Twig/Tests/Fixtures/filters/sort.test
Expand Up @@ -3,8 +3,10 @@
--TEMPLATE--
{{ array1|sort|join }}
{{ array2|sort|join }}
{{ traversable|sort|join }}
--DATA--
return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'))
return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'), 'traversable' => new ArrayObject(array(0 => 3, 1 => 2, 2 => 1)))
--EXPECT--
14
barfoo
123

0 comments on commit 542dd3a

Please sign in to comment.