Rename array to sequence

This commit is contained in:
Fabien Potencier
2024-06-16 19:50:38 +02:00
parent 5157402a77
commit c44be99a84
22 changed files with 102 additions and 87 deletions
+3
View File
@@ -4,6 +4,9 @@
* Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of * Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of
`Twig\ExpressionParser::parseMappingExpression()` `Twig\ExpressionParser::parseMappingExpression()`
* Deprecate `Twig\ExpressionParser\parseArrayExpression()`` in favor of
`Twig\ExpressionParser::parseSequenceExpression()`
# 3.10.3 (2024-05-16) # 3.10.3 (2024-05-16)
* Fix missing ; in generated code * Fix missing ; in generated code
+3 -3
View File
@@ -40,7 +40,7 @@ standards:
{{ foo ~ bar }} {{ foo ~ bar }}
{{ true ? true : false }} {{ true ? true : false }}
* Put exactly one space after the ``:`` sign in mappings and ``,`` in arrays * Put exactly one space after the ``:`` sign in mappings and ``,`` in sequences
and mappings: and mappings:
.. code-block:: twig .. code-block:: twig
@@ -80,8 +80,8 @@ standards:
{{ foo|default('foo') }} {{ foo|default('foo') }}
{{ range(1..10) }} {{ range(1..10) }}
* Do not put any spaces before and after the opening and the closing of arrays * Do not put any spaces before and after the opening and the closing of
and mappings: sequences and mappings:
.. code-block:: twig .. code-block:: twig
+3
View File
@@ -51,6 +51,9 @@ Parser
* The ``Twig\ExpressionParser::parseHashExpression()`` method is deprecated, use * The ``Twig\ExpressionParser::parseHashExpression()`` method is deprecated, use
``Twig\ExpressionParser::parseMappingExpression()`` instead. ``Twig\ExpressionParser::parseMappingExpression()`` instead.
* The ``Twig\ExpressionParser::parseArrayExpression()`` method is deprecated, use
``Twig\ExpressionParser::parseSequenceExpression()`` instead.
Templates Templates
--------- ---------
+1 -1
View File
@@ -50,6 +50,6 @@ Arguments
--------- ---------
* ``mime``: The mime type * ``mime``: The mime type
* ``parameters``: An array of parameters * ``parameters``: A mapping of parameters
.. _RFC 2397: https://tools.ietf.org/html/rfc2397 .. _RFC 2397: https://tools.ietf.org/html/rfc2397
+10 -4
View File
@@ -1,14 +1,20 @@
``keys`` ``keys``
======== ========
The ``keys`` filter returns the keys of an array. It is useful when you want to The ``keys`` filter returns the keys of a sequence or a mapping. It is useful
iterate over the keys of an array: when you want to iterate over the keys of a sequence or a mapping:
.. code-block:: twig .. code-block:: twig
{% for key in array|keys %} {% for key in [1, 2, 3, 4]|keys %}
... {{ key }}
{% endfor %} {% endfor %}
{# outputs: 1 2 3 4 #}
{% for key in {a: 'a_value', b: 'b_value'}|keys %}
{{ key }}
{% endfor %}
{# outputs: a b #}
.. note:: .. note::
+4 -2
View File
@@ -1,7 +1,9 @@
``merge`` ``merge``
========= =========
The ``merge`` filter merges an array with another array: The ``merge`` filter merges sequences and mappings.
The ``merge`` filter also works on sequences:
.. code-block:: twig .. code-block:: twig
@@ -29,7 +31,7 @@ overridden.
.. tip:: .. tip::
If you want to ensure that some values are defined in an array (by given If you want to ensure that some values are defined in a mapping (by given
default values), reverse the two elements in the call: default values), reverse the two elements in the call:
.. code-block:: twig .. code-block:: twig
+2 -2
View File
@@ -1,7 +1,7 @@
``sort`` ``sort``
======== ========
The ``sort`` filter sorts an array: The ``sort`` filter sorts sequences and mappings:
.. code-block:: twig .. code-block:: twig
@@ -15,7 +15,7 @@ The ``sort`` filter sorts an array:
association. It supports Traversable objects by transforming association. It supports Traversable objects by transforming
those to arrays. those to arrays.
You can pass an arrow function to sort the array: You can pass an arrow function to configure the sorting:
.. code-block:: html+twig .. code-block:: html+twig
+1 -1
View File
@@ -11,7 +11,7 @@ of strings:
You can also pass a ``limit`` argument: You can also pass a ``limit`` argument:
* If ``limit`` is positive, the returned array will contain a maximum of * If ``limit`` is positive, the returned sequence will contain a maximum of
limit elements with the last element containing the rest of string; limit elements with the last element containing the rest of string;
* If ``limit`` is negative, all components except the last -limit are * If ``limit`` is negative, all components except the last -limit are
+2 -2
View File
@@ -1,8 +1,8 @@
``url_encode`` ``url_encode``
============== ==============
The ``url_encode`` filter percent encodes a given string as URL segment The ``url_encode`` filter percent encodes a given string as URL segment or a
or an array as query string: mapping as query string:
.. code-block:: twig .. code-block:: twig
+1 -1
View File
@@ -1,7 +1,7 @@
``cycle`` ``cycle``
========= =========
The ``cycle`` function cycles on an array of values: The ``cycle`` function cycles on a sequence or mapping:
.. code-block:: twig .. code-block:: twig
+4 -4
View File
@@ -1,8 +1,8 @@
``for`` ``for``
======= =======
Loop over each item in a sequence. For example, to display a list of users Loop over each item in a sequence or a mapping. For example, to display a list
provided in a variable called ``users``: of users provided in a variable called ``users``:
.. code-block:: html+twig .. code-block:: html+twig
@@ -15,8 +15,8 @@ provided in a variable called ``users``:
.. note:: .. note::
A sequence can be either an array or an object implementing the A sequence or a mapping can be either an array or an object implementing
``Traversable`` interface. the ``Traversable`` interface.
If you do need to iterate over a sequence of numbers, you can use the ``..`` If you do need to iterate over a sequence of numbers, you can use the ``..``
operator: operator:
+5 -3
View File
@@ -12,7 +12,7 @@ In the simplest form you can use it to test if an expression evaluates to
<p>Our website is in maintenance mode. Please, come back later.</p> <p>Our website is in maintenance mode. Please, come back later.</p>
{% endif %} {% endif %}
You can also test if an array is not empty: You can also test if a sequence or a mapping is not empty:
.. code-block:: html+twig .. code-block:: html+twig
@@ -71,8 +71,10 @@ use more complex ``expressions`` there too:
INF (Infinity) true INF (Infinity) true
whitespace-only string true whitespace-only string true
string "0" or '0' false string "0" or '0' false
empty array false empty sequence false
empty mapping false
null false null false
non-empty array true non-empty sequence true
non-empty mapping true
object true object true
====================== ==================== ====================== ====================
+8 -8
View File
@@ -102,7 +102,7 @@ If a variable or attribute does not exist, the behavior depends on the
For convenience's sake ``foo.bar`` does the following things on the PHP For convenience's sake ``foo.bar`` does the following things on the PHP
layer: layer:
* check if ``foo`` is an array and ``bar`` a valid element; * check if ``foo`` is a sequence or a mapping and ``bar`` a valid element;
* if not, and if ``foo`` is an object, check that ``bar`` is a valid property; * if not, and if ``foo`` is an object, check that ``bar`` is a valid property;
* if not, and if ``foo`` is an object, check that ``bar`` is a valid method * if not, and if ``foo`` is an object, check that ``bar`` is a valid method
(even if ``bar`` is the constructor - use ``__construct()`` instead); (even if ``bar`` is the constructor - use ``__construct()`` instead);
@@ -115,7 +115,7 @@ If a variable or attribute does not exist, the behavior depends on the
Twig also supports a specific syntax for accessing items on PHP arrays, Twig also supports a specific syntax for accessing items on PHP arrays,
``foo['bar']``: ``foo['bar']``:
* check if ``foo`` is an array and ``bar`` a valid element; * check if ``foo`` is a sequence or a mapping and ``bar`` a valid element;
* if not, and if ``strict_variables`` is ``false``, return ``null``; * if not, and if ``strict_variables`` is ``false``, return ``null``;
* if not, throw an exception. * if not, throw an exception.
@@ -531,7 +531,7 @@ exist:
writing the number down. If a dot is present the number is a float, writing the number down. If a dot is present the number is a float,
otherwise an integer. otherwise an integer.
* ``["foo", "bar"]``: Arrays are defined by a sequence of expressions * ``["foo", "bar"]``: Sequences are defined by a sequence of expressions
separated by a comma (``,``) and wrapped with squared brackets (``[]``). separated by a comma (``,``) and wrapped with squared brackets (``[]``).
* ``{"foo": "bar"}``: Mappings are defined by a list of keys and values * ``{"foo": "bar"}``: Mappings are defined by a list of keys and values
@@ -563,7 +563,7 @@ exist:
* ``null``: ``null`` represents no specific value. This is the value returned * ``null``: ``null`` represents no specific value. This is the value returned
when a variable does not exist. ``none`` is an alias for ``null``. when a variable does not exist. ``none`` is an alias for ``null``.
Arrays and mappings can be nested: Sequences and mappings can be nested:
.. code-block:: twig .. code-block:: twig
@@ -698,8 +698,8 @@ operand is contained in the right:
.. tip:: .. tip::
You can use this filter to perform a containment test on strings, arrays, You can use this filter to perform a containment test on strings,
or objects implementing the ``Traversable`` interface. sequences, mappings, or objects implementing the ``Traversable`` interface.
To perform a negative test, use the ``not in`` operator: To perform a negative test, use the ``not in`` operator:
@@ -785,7 +785,7 @@ The following operators don't fit into any of the other categories:
{# returns the value of foo if it is defined and not null, 'no' otherwise #} {# returns the value of foo if it is defined and not null, 'no' otherwise #}
{{ foo ?? 'no' }} {{ foo ?? 'no' }}
* ``...``: The spread operator can be used to expand arrays or mappings (it * ``...``: The spread operator can be used to expand sequences or mappings (it
cannot be used to expand the arguments of a function call): cannot be used to expand the arguments of a function call):
.. code-block:: twig .. code-block:: twig
@@ -827,7 +827,7 @@ Operator Score of precedence Description
``**`` 200 Raises a number to the power of another ``**`` 200 Raises a number to the power of another
``??`` 300 Default value when a variable is null ``??`` 300 Default value when a variable is null
``+``, ``-`` 500 Unary operations on numbers ``+``, ``-`` 500 Unary operations on numbers
``|``,``[]``,``.`` - Filters, array, mapping, and attribute access ``|``,``[]``,``.`` - Filters, sequence, mapping, and attribute access
============================= =================================== ===================================================== ============================= =================================== =====================================================
Without using any parentheses, the operator precedence rules are used to Without using any parentheses, the operator precedence rules are used to
+1 -1
View File
@@ -1,7 +1,7 @@
``empty`` ``empty``
========= =========
``empty`` checks if a variable is an empty string, an empty array, an empty ``empty`` checks if a variable is an empty string, an empty sequence, an empty
mapping, exactly ``false``, or exactly ``null``. mapping, exactly ``false``, or exactly ``null``.
For objects that implement the ``Countable`` interface, ``empty`` will check the For objects that implement the ``Countable`` interface, ``empty`` will check the
+12 -5
View File
@@ -278,7 +278,7 @@ class ExpressionParser
// no break // no break
default: default:
if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
$node = $this->parseArrayExpression(); $node = $this->parseSequenceExpression();
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
$node = $this->parseMappingExpression(); $node = $this->parseMappingExpression();
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
@@ -320,15 +320,22 @@ class ExpressionParser
} }
public function parseArrayExpression() public function parseArrayExpression()
{
trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__);
return $this->parseSequenceExpression();
}
public function parseSequenceExpression()
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'A sequence element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine()); $node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true; $first = true;
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
if (!$first) { if (!$first) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A sequence element must be followed by a comma');
// trailing ,? // trailing ,?
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
@@ -346,7 +353,7 @@ class ExpressionParser
$node->addElement($this->parseExpression()); $node->addElement($this->parseExpression());
} }
} }
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened sequence is not properly closed');
return $node; return $node;
} }
@@ -641,7 +648,7 @@ class ExpressionParser
$value = $this->parsePrimaryExpression(); $value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) { if (!$this->checkConstantExpression($value)) {
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
} }
} else { } else {
$value = $this->parseExpression(0, $allowArrow); $value = $this->parseExpression(0, $allowArrow);
+12 -12
View File
@@ -329,7 +329,7 @@ final class CoreExtension extends AbstractExtension
} }
if (!\count($values)) { if (!\count($values)) {
throw new RuntimeError('The "cycle" function does not work on empty arrays.'); throw new RuntimeError('The "cycle" function does not work on empty sequences/mappings.');
} }
return $values[$position % \count($values)]; return $values[$position % \count($values)];
@@ -399,7 +399,7 @@ final class CoreExtension extends AbstractExtension
$values = self::toArray($values); $values = self::toArray($values);
if (0 === \count($values)) { if (0 === \count($values)) {
throw new RuntimeError('The random function cannot pick from an empty array.'); throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.');
} }
return $values[array_rand($values, 1)]; return $values[array_rand($values, 1)];
@@ -536,7 +536,7 @@ final class CoreExtension extends AbstractExtension
public static function replace($str, $from): string public static function replace($str, $from): string
{ {
if (!is_iterable($from)) { if (!is_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))); throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from)));
} }
return strtr($str ?? '', self::toArray($from)); return strtr($str ?? '', self::toArray($from));
@@ -633,7 +633,7 @@ final class CoreExtension extends AbstractExtension
foreach ($arrays as $argNumber => $array) { foreach ($arrays as $argNumber => $array) {
if (!is_iterable($array)) { if (!is_iterable($array)) {
throw new RuntimeError(\sprintf('The merge filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1));
} }
$result = array_merge($result, self::toArray($array)); $result = array_merge($result, self::toArray($array));
@@ -907,7 +907,7 @@ final class CoreExtension extends AbstractExtension
if ($array instanceof \Traversable) { if ($array instanceof \Traversable) {
$array = iterator_to_array($array); $array = iterator_to_array($array);
} elseif (!\is_array($array)) { } elseif (!\is_array($array)) {
throw new RuntimeError(\sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); throw new RuntimeError(\sprintf('The sort filter only works with sequences/mappings or "Traversable", got "%s".', \gettype($array)));
} }
if (null !== $arrow) { if (null !== $arrow) {
@@ -1424,7 +1424,7 @@ final class CoreExtension extends AbstractExtension
public static function batch($items, $size, $fill = null, $preserveKeys = true): array public static function batch($items, $size, $fill = null, $preserveKeys = true): array
{ {
if (!is_iterable($items)) { if (!is_iterable($items)) {
throw new RuntimeError(\sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items)));
} }
$size = ceil($size); $size = ceil($size);
@@ -1491,9 +1491,9 @@ final class CoreExtension extends AbstractExtension
$message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object));
} elseif (\is_array($object)) { } elseif (\is_array($object)) {
if (empty($object)) { if (empty($object)) {
$message = \sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem);
} else { } else {
$message = \sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object)));
} }
} elseif (/* Template::ARRAY_CALL */ 'array' === $type) { } elseif (/* Template::ARRAY_CALL */ 'array' === $type) {
if (null === $object) { if (null === $object) {
@@ -1523,7 +1523,7 @@ final class CoreExtension extends AbstractExtension
if (null === $object) { if (null === $object) {
$message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); $message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item);
} elseif (\is_array($object)) { } elseif (\is_array($object)) {
$message = \sprintf('Impossible to invoke a method ("%s") on an array.', $item); $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.', $item);
} else { } else {
$message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
} }
@@ -1661,7 +1661,7 @@ final class CoreExtension extends AbstractExtension
if ($array instanceof \Traversable) { if ($array instanceof \Traversable) {
$array = iterator_to_array($array); $array = iterator_to_array($array);
} elseif (!\is_array($array)) { } elseif (!\is_array($array)) {
throw new RuntimeError(\sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array)));
} }
return array_column($array, $name, $index); return array_column($array, $name, $index);
@@ -1673,7 +1673,7 @@ final class CoreExtension extends AbstractExtension
public static function filter(Environment $env, $array, $arrow) public static function filter(Environment $env, $array, $arrow)
{ {
if (!is_iterable($array)) { if (!is_iterable($array)) {
throw new RuntimeError(\sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
} }
self::checkArrowInSandbox($env, $arrow, 'filter', 'filter'); self::checkArrowInSandbox($env, $arrow, 'filter', 'filter');
@@ -1709,7 +1709,7 @@ final class CoreExtension extends AbstractExtension
self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter');
if (!\is_array($array) && !$array instanceof \Traversable) { if (!\is_array($array) && !$array instanceof \Traversable) {
throw new RuntimeError(\sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array)));
} }
$accumulator = $initial; $accumulator = $initial;
+12 -12
View File
@@ -55,9 +55,9 @@ class ExpressionParserTest extends TestCase
} }
/** /**
* @dataProvider getTestsForArray * @dataProvider getTestsForSequence
*/ */
public function testArrayExpression($template, $expected) public function testSequenceExpression($template, $expected)
{ {
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]); $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
$stream = $env->tokenize($source = new Source($template, '')); $stream = $env->tokenize($source = new Source($template, ''));
@@ -68,9 +68,9 @@ class ExpressionParserTest extends TestCase
} }
/** /**
* @dataProvider getFailingTestsForArray * @dataProvider getFailingTestsForSequence
*/ */
public function testArraySyntaxError($template) public function testSequenceSyntaxError($template)
{ {
$this->expectException(SyntaxError::class); $this->expectException(SyntaxError::class);
@@ -79,7 +79,7 @@ class ExpressionParserTest extends TestCase
$parser->parse($env->tokenize(new Source($template, 'index'))); $parser->parse($env->tokenize(new Source($template, 'index')));
} }
public function getFailingTestsForArray() public function getFailingTestsForSequence()
{ {
return [ return [
['{{ [1, "a": "b"] }}'], ['{{ [1, "a": "b"] }}'],
@@ -88,10 +88,10 @@ class ExpressionParserTest extends TestCase
]; ];
} }
public function getTestsForArray() public function getTestsForSequence()
{ {
return [ return [
// simple array // simple sequence
['{{ [1, 2] }}', new ArrayExpression([ ['{{ [1, 2] }}', new ArrayExpression([
new ConstantExpression(0, 1), new ConstantExpression(0, 1),
new ConstantExpression(1, 1), new ConstantExpression(1, 1),
@@ -101,7 +101,7 @@ class ExpressionParserTest extends TestCase
], 1), ], 1),
], ],
// array with trailing , // sequence with trailing ,
['{{ [1, 2, ] }}', new ArrayExpression([ ['{{ [1, 2, ] }}', new ArrayExpression([
new ConstantExpression(0, 1), new ConstantExpression(0, 1),
new ConstantExpression(1, 1), new ConstantExpression(1, 1),
@@ -131,7 +131,7 @@ class ExpressionParserTest extends TestCase
], 1), ], 1),
], ],
// mapping in an array // mapping in a sequence
['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([ ['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([
new ConstantExpression(0, 1), new ConstantExpression(0, 1),
new ConstantExpression(1, 1), new ConstantExpression(1, 1),
@@ -147,7 +147,7 @@ class ExpressionParserTest extends TestCase
], 1), ], 1),
], ],
// array in a mapping // sequence in a mapping
['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([ ['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([
new ConstantExpression('a', 1), new ConstantExpression('a', 1),
new ArrayExpression([ new ArrayExpression([
@@ -168,7 +168,7 @@ class ExpressionParserTest extends TestCase
new NameExpression('b', 1), new NameExpression('b', 1),
], 1)], ], 1)],
// array with spread operator // sequence with spread operator
['{{ [1, 2, ...foo] }}', ['{{ [1, 2, ...foo] }}',
new ArrayExpression([ new ArrayExpression([
new ConstantExpression(0, 1), new ConstantExpression(0, 1),
@@ -301,7 +301,7 @@ class ExpressionParserTest extends TestCase
public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template) public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template)
{ {
$this->expectException(SyntaxError::class); $this->expectException(SyntaxError::class);
$this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1'); $this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping) in "index" at line 1');
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]); $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
$parser = new Parser($env); $parser = new Parser($env);
@@ -9,4 +9,4 @@ Exception thrown from a child for an extension error
--DATA-- --DATA--
return [] return []
--EXCEPTION-- --EXCEPTION--
Twig\Error\RuntimeError: The random function cannot pick from an empty array in "base.twig" at line 4. Twig\Error\RuntimeError: The random function cannot pick from an empty sequence/mapping in "base.twig" at line 4.
@@ -9,4 +9,4 @@ Exception thrown from an include for an extension error
--DATA-- --DATA--
return [] return []
--EXCEPTION-- --EXCEPTION--
Twig\Error\RuntimeError: The random function cannot pick from an empty array in "content.twig" at line 4. Twig\Error\RuntimeError: The random function cannot pick from an empty sequence/mapping in "content.twig" at line 4.
@@ -5,4 +5,4 @@ Exception for invalid argument type in replace call
--DATA-- --DATA--
return ['stdClass' => new \stdClass()] return ['stdClass' => new \stdClass()]
--EXCEPTION-- --EXCEPTION--
Twig\Error\RuntimeError: The "replace" filter expects an array or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2. Twig\Error\RuntimeError: The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2.
@@ -1,8 +0,0 @@
--TEST--
"cycle" function returns an error on empty arrays
--TEMPLATE--
{{ cycle([], 0) }}
--DATA--
return []
--EXCEPTION--
Twig\Error\RuntimeError: The "cycle" function does not work on empty arrays in "index.twig" at line 2.
+15 -15
View File
@@ -66,17 +66,17 @@ class TemplateTest extends TestCase
return [ return [
['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'],
['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'], ['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'],
['{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], ['{{ empty_array["a"] }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'],
['{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], ['{{ array["a"] }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig\Tests\TemplateArrayAccessObject" does not exist in "%s" at line 1.'], ['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig\Tests\TemplateArrayAccessObject" does not exist in "%s" at line 1.'],
['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'],
['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'],
['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'], ['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'],
['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'], ['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'],
['{{ array.a() }}', 'Impossible to invoke a method ("a") on an array in "%s" at line 1.'], ['{{ array.a() }}', 'Impossible to invoke a method ("a") on a sequence/mapping in "%s" at line 1.'],
['{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], ['{{ empty_array.a }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'],
['{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], ['{{ array.a }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1.'], ['{{ attribute(array, -10) }}', 'Key "-10" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()"/"hasa()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'], ['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()"/"hasa()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()"/"hasmissing_method()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'], ['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()"/"hasmissing_method()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'], ['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'],
@@ -193,14 +193,14 @@ class TemplateTest extends TestCase
$this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);
$this->assertSame('EmptyString', $array[null]); $this->assertSame('EmptyString', $array[null]);
$this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)'); $this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)'); $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)'); $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)'); $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '"1" is treated as integer 1 when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)'); $this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)'); $this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)'); $this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '"01" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');
$this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as "" when accessing an array (equals PHP behavior)'); $this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as "" when accessing a sequence/mapping (equals PHP behavior)');
} }
/** /**
@@ -395,7 +395,7 @@ class TemplateTest extends TestCase
$tests = array_merge($tests, [ $tests = array_merge($tests, [
[false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'], [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'],
[false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'], [false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'],
[false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the array is empty in "index.twig".'], [false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the sequence/mapping is empty in "index.twig".'],
]); ]);
return $tests; return $tests;