diff --git a/CHANGELOG b/CHANGELOG index 03d69526e..06b1404a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,7 +15,8 @@ * 1.21.2 (2015-XX-XX) - * n/a + * added Traversable support for replace, merge, and sort + * deprecated support for character by character replacement for the "replace" filter * 1.21.1 (2015-08-26) diff --git a/doc/filters/merge.rst b/doc/filters/merge.rst index cb8b1b223..88780dd6f 100644 --- a/doc/filters/merge.rst +++ b/doc/filters/merge.rst @@ -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 diff --git a/doc/filters/sort.rst b/doc/filters/sort.rst index 33311528f..350207f8e 100644 --- a/doc/filters/sort.rst +++ b/doc/filters/sort.rst @@ -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 diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index 352a92d51..675fdb285 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -576,7 +576,7 @@ class Twig_ExpressionParser if ($function->isDeprecated()) { $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); - if ($test->getAlternative()) { + if ($function->getAlternative()) { $message .= sprintf('. Use "%s" instead', $function->getAlternative()); } $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line); @@ -602,7 +602,7 @@ class Twig_ExpressionParser if ($filter->isDeprecated()) { $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); - if ($test->getAlternative()) { + if ($filter->getAlternative()) { $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); } $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line); diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index f34949037..51e2c93b2 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -152,7 +152,7 @@ class Twig_Extension_Core extends Twig_Extension new Twig_Filter('date', 'twig_date_format_filter', array('needs_environment' => true)), new Twig_Filter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)), new Twig_Filter('format', 'sprintf'), - new Twig_Filter('replace', 'strtr'), + new Twig_Filter('replace', 'twig_replace_filter'), new Twig_Filter('number_format', 'twig_number_format_filter', array('needs_environment' => true)), new Twig_Filter('abs', 'abs'), new Twig_Filter('round', 'twig_round'), @@ -527,6 +527,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.0', 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. * @@ -636,15 +660,23 @@ function twig_jsonencode_filter($value, $options = 0) * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} * * - * @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); @@ -828,7 +860,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)); } @@ -850,7 +882,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); } @@ -882,12 +914,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; diff --git a/test/Twig/Tests/Fixtures/filters/merge.test b/test/Twig/Tests/Fixtures/filters/merge.test index 2bd3d4c04..81371a41b 100644 --- a/test/Twig/Tests/Fixtures/filters/merge.test +++ b/test/Twig/Tests/Fixtures/filters/merge.test @@ -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 diff --git a/test/Twig/Tests/Fixtures/filters/replace.test b/test/Twig/Tests/Fixtures/filters/replace.test index 4021660be..06be7e270 100644 --- a/test/Twig/Tests/Fixtures/filters/replace.test +++ b/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. diff --git a/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test b/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test new file mode 100644 index 000000000..08ecfd490 --- /dev/null +++ b/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. diff --git a/test/Twig/Tests/Fixtures/filters/sort.test b/test/Twig/Tests/Fixtures/filters/sort.test index 21d575f18..c67c18ea9 100644 --- a/test/Twig/Tests/Fixtures/filters/sort.test +++ b/test/Twig/Tests/Fixtures/filters/sort.test @@ -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