Make "length" filter and "empty" test consider __toString [Twig 1.x]

This commit is contained in:
Matthias Pigulla
2017-03-10 13:14:36 +01:00
committed by Fabien Potencier
parent c5ba65ca44
commit f5193e92c5
7 changed files with 146 additions and 35 deletions
+7
View File
@@ -1,3 +1,10 @@
* 1.33.0 (2017-XX-XX)
* "length" filter now returns string length when applied to an object that does
not implement \Countable but provides __toString()
* "empty" test will now consider the return value of the __toString() method for
objects implement __toString() but not \Countable
* 1.32.1 (2017-XX-XX)
* n/a
+11 -1
View File
@@ -1,8 +1,18 @@
``length``
==========
.. versionadded:: 1.33
Support for the ``__toString()`` magic method has been added in Twig 1.33.
The ``length`` filter returns the number of items of a sequence or mapping, or
the length of a string:
the length of a string.
For objects that implement the ``Countable`` interface, ``length`` will use the
return value of the ``count()`` method.
For objects that implement the ``__toString()`` magic method (and not ``Countable``),
it will return the length of the string provided by that method.
.. code-block:: jinja
+12 -1
View File
@@ -1,11 +1,22 @@
``empty``
=========
.. versionadded:: 1.33
Support for the ``__toString()`` magic method has been added in Twig 1.33.
``empty`` checks if a variable is an empty string, an empty array, an empty
hash, exactly ``false``, or exactly ``null``:
hash, exactly ``false``, or exactly ``null``.
For objects that implement the ``Countable`` interface, ``empty`` will check the
return value of the ``count()`` method.
For objects that implement the ``__toString()`` magic method (and not ``Countable``),
it will check if an empty string is returned.
.. code-block:: jinja
{% if foo is empty %}
...
{% endif %}
+22 -2
View File
@@ -1259,7 +1259,15 @@ if (function_exists('mb_get_info')) {
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
if (is_scalar($thing)) {
return mb_strlen($thing, $env->getCharset());
}
if (method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return mb_strlen((string) $thing, $env->getCharset());
}
return count($thing);
}
/**
@@ -1342,7 +1350,15 @@ else {
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
return is_scalar($thing) ? strlen($thing) : count($thing);
if (is_scalar($thing)) {
return strlen($thing);
}
if (method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return strlen((string) $thing);
}
return count($thing);
}
/**
@@ -1404,6 +1420,10 @@ function twig_test_empty($value)
return 0 == count($value);
}
if (method_exists($value, '__toString')) {
return '' === (string) $value;
}
return '' === $value || false === $value || null === $value || array() === $value;
}
+14 -3
View File
@@ -4,11 +4,22 @@
{{ array|length }}
{{ string|length }}
{{ number|length }}
{{ markup|length }}
{{ to_string_able|length }}
{{ countable|length }}
{{ magic|length }}
--DATA--
return array('array' => array(1, 4), 'string' => 'foo', 'number' => 1000, 'markup' => new Twig_Markup('foo', 'UTF-8'))
return array(
'array' => array(1, 4),
'string' => 'foo',
'number' => 1000,
'to_string_able' => new ToStringStub('foobar'),
'countable' => new CountableStub(42), /* also asserts we do *not* call __toString() */
'magic' => new MagicCallStub(), /* used to assert we do *not* call __call */
);
--EXPECT--
2
3
4
3
6
42
1
+25 -28
View File
@@ -1,35 +1,28 @@
--TEST--
"empty" test
--TEMPLATE--
{{ foo is empty ? 'ok' : 'ko' }}
{{ bar is empty ? 'ok' : 'ko' }}
{{ foobar is empty ? 'ok' : 'ko' }}
{{ array is empty ? 'ok' : 'ko' }}
{{ zero is empty ? 'ok' : 'ko' }}
{{ string is empty ? 'ok' : 'ko' }}
{{ string_empty is empty ? 'ok' : 'ko' }}
{{ string_zero is empty ? 'ko' : 'ok' }}
{{ value_null is empty ? 'ok' : 'ko' }}
{{ value_false is empty ? 'ok' : 'ko' }}
{{ value_int_zero is empty ? 'ko' : 'ok' }}
{{ array_empty is empty ? 'ok' : 'ko' }}
{{ array_not_empty is empty ? 'ko' : 'ok' }}
{{ magically_callable is empty ? 'ko' : 'ok' }}
{{ countable_empty is empty ? 'ok' : 'ko' }}
{{ countable_not_empty is empty ? 'ok' : 'ko' }}
{{ countable_not_empty is empty ? 'ko' : 'ok' }}
{{ tostring_empty is empty ? 'ok' : 'ko' }}
{{ tostring_not_empty is empty ? 'ko' : 'ok' }}
{{ markup_empty is empty ? 'ok' : 'ko' }}
{{ markup_not_empty is empty ? 'ok' : 'ko' }}
{{ markup_not_empty is empty ? 'ko' : 'ok' }}
--DATA--
class CountableStub implements Countable
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function count()
{
return count($this->items);
}
}
return array(
'foo' => '', 'bar' => null, 'foobar' => false, 'array' => array(), 'zero' => 0, 'string' => '0',
'string_empty' => '', 'string_zero' => '0',
'value_null' => null, 'value_false' => false, 'value_int_zero' => 0,
'array_empty' => array(), 'array_not_empty' => array(1, 2),
'magically_callable' => new MagicCallStub(),
'countable_empty' => new CountableStub(array()), 'countable_not_empty' => new CountableStub(array(1, 2)),
'tostring_empty' => new ToStringStub(''), 'tostring_not_empty' => new ToStringStub('0' /* edge case of using "0" as the string */),
'markup_empty' => new Twig_Markup('', 'UTF-8'), 'markup_not_empty' => new Twig_Markup('test', 'UTF-8'),
);
--EXPECT--
@@ -37,9 +30,13 @@ ok
ok
ok
ok
ko
ko
ok
ko
ok
ko
ok
ok
ok
ok
ok
ok
ok
ok
+55
View File
@@ -252,3 +252,58 @@ class TwigTestExtension extends Twig_Extension
return 'static_magic_'.$arguments[0];
}
}
/**
* This class is used in tests for the "length" filter and "empty" test. It asserts that __call is not
* used to convert such objects to strings.
*/
class MagicCallStub
{
public function __call($name, $args)
{
throw new Exception('__call shall not be called');
}
}
class ToStringStub
{
/**
* @var string
*/
private $string;
public function __construct($string)
{
$this->string = $string;
}
public function __toString()
{
return $this->string;
}
}
/**
* This class is used in tests for the length filter and empty test to show
* that when \Countable is implemented, it is preferred over the __toString()
* method.
*/
class CountableStub implements \Countable
{
private $count;
public function __construct($count)
{
$this->count = $count;
}
public function count()
{
return $this->count;
}
public function __toString()
{
throw new Exception('__toString shall not be called on \Countables');
}
}