mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-31 04:27:00 +00:00
Merge branch '3.x' into 4.x
* 3.x: Fix typo Tweak Rename array to sequence Rename hash to mapping
This commit is contained in:
@@ -40,8 +40,8 @@ standards:
|
||||
{{ foo ~ bar }}
|
||||
{{ true ? true : false }}
|
||||
|
||||
* Put exactly one space after the ``:`` sign in hashes and ``,`` in
|
||||
arrays and hashes:
|
||||
* Put exactly one space after the ``:`` sign in mappings and ``,`` in sequences
|
||||
and mappings:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -80,8 +80,8 @@ standards:
|
||||
{{ foo|default('foo') }}
|
||||
{{ range(1..10) }}
|
||||
|
||||
* Do not put any spaces before and after the opening and the closing of arrays
|
||||
and hashes:
|
||||
* Do not put any spaces before and after the opening and the closing of
|
||||
sequences and mappings:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
|
||||
@@ -50,6 +50,6 @@ Arguments
|
||||
---------
|
||||
|
||||
* ``mime``: The mime type
|
||||
* ``parameters``: An array of parameters
|
||||
* ``parameters``: A mapping of parameters
|
||||
|
||||
.. _RFC 2397: https://tools.ietf.org/html/rfc2397
|
||||
|
||||
+10
-4
@@ -1,14 +1,20 @@
|
||||
``keys``
|
||||
========
|
||||
|
||||
The ``keys`` filter returns the keys of an array. It is useful when you want to
|
||||
iterate over the keys of an array:
|
||||
The ``keys`` filter returns the keys of a sequence or a mapping. It is useful
|
||||
when you want to iterate over the keys of a sequence or a mapping:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% for key in array|keys %}
|
||||
...
|
||||
{% for key in [1, 2, 3, 4]|keys %}
|
||||
{{ key }}
|
||||
{% endfor %}
|
||||
{# outputs: 1 2 3 4 #}
|
||||
|
||||
{% for key in {a: 'a_value', b: 'b_value'}|keys %}
|
||||
{{ key }}
|
||||
{% endfor %}
|
||||
{# outputs: a b #}
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
``merge``
|
||||
=========
|
||||
|
||||
The ``merge`` filter merges an array with another array:
|
||||
The ``merge`` filter merges sequences and mappings:
|
||||
|
||||
For sequences, new values are added at the end of the existing ones:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -11,9 +13,9 @@ The ``merge`` filter merges an array with another array:
|
||||
|
||||
{# values now contains [1, 2, 'apple', 'orange'] #}
|
||||
|
||||
New values are added at the end of the existing ones.
|
||||
|
||||
The ``merge`` filter also works on hashes:
|
||||
For mappings, the merging process occurs on the keys; if the key does not
|
||||
already exist, it is added but if the key already exists, its value is
|
||||
overridden:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -23,13 +25,9 @@ The ``merge`` filter also works on hashes:
|
||||
|
||||
{# items now contains {'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car'} #}
|
||||
|
||||
For hashes, the merging process occurs on the keys: if the key does not
|
||||
already exist, it is added but if the key already exists, its value is
|
||||
overridden.
|
||||
|
||||
.. 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:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -17,7 +17,7 @@ format is free-form):
|
||||
Arguments
|
||||
---------
|
||||
|
||||
* ``from``: The placeholder values as a hash
|
||||
* ``from``: The placeholder values as a mapping
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
``sort``
|
||||
========
|
||||
|
||||
The ``sort`` filter sorts an array:
|
||||
The ``sort`` filter sorts sequences and mappings:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -15,7 +15,7 @@ The ``sort`` filter sorts an array:
|
||||
association. It supports Traversable objects by transforming
|
||||
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
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ of strings:
|
||||
|
||||
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;
|
||||
|
||||
* If ``limit`` is negative, all components except the last -limit are
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
``url_encode``
|
||||
==============
|
||||
|
||||
The ``url_encode`` filter percent encodes a given string as URL segment
|
||||
or an array as query string:
|
||||
The ``url_encode`` filter percent encodes a given string as URL segment or a
|
||||
mapping as query string:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
``cycle``
|
||||
=========
|
||||
|
||||
The ``cycle`` function cycles on an array of values:
|
||||
The ``cycle`` function cycles on a sequence or mapping:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
``for``
|
||||
=======
|
||||
|
||||
Loop over each item in a sequence. For example, to display a list of users
|
||||
provided in a variable called ``users``:
|
||||
Loop over each item in a sequence or a mapping. For example, to display a list
|
||||
of users provided in a variable called ``users``:
|
||||
|
||||
.. code-block:: html+twig
|
||||
|
||||
@@ -15,8 +15,8 @@ provided in a variable called ``users``:
|
||||
|
||||
.. note::
|
||||
|
||||
A sequence can be either an array or an object implementing the
|
||||
``Traversable`` interface.
|
||||
A sequence or a mapping can be either an array or an object implementing
|
||||
the ``Traversable`` interface.
|
||||
|
||||
If you do need to iterate over a sequence of numbers, you can use the ``..``
|
||||
operator:
|
||||
|
||||
+5
-3
@@ -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>
|
||||
{% 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
|
||||
|
||||
@@ -71,8 +71,10 @@ use more complex ``expressions`` there too:
|
||||
INF (Infinity) true
|
||||
whitespace-only string true
|
||||
string "0" or '0' false
|
||||
empty array false
|
||||
empty sequence false
|
||||
empty mapping false
|
||||
null false
|
||||
non-empty array true
|
||||
non-empty sequence true
|
||||
non-empty mapping true
|
||||
object true
|
||||
====================== ====================
|
||||
|
||||
+3
-3
@@ -13,8 +13,8 @@ scope are not visible outside of the scope:
|
||||
foo is not visible here any longer
|
||||
|
||||
Instead of defining variables at the beginning of the scope, you can pass a
|
||||
hash of variables you want to define in the ``with`` tag; the previous example
|
||||
is equivalent to the following one:
|
||||
mapping of variables you want to define in the ``with`` tag; the previous
|
||||
example is equivalent to the following one:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -23,7 +23,7 @@ is equivalent to the following one:
|
||||
{% endwith %}
|
||||
foo is not visible here any longer
|
||||
|
||||
{# it works with any expression that resolves to a hash #}
|
||||
{# it works with any expression that resolves to a mapping #}
|
||||
{% set vars = {foo: 42} %}
|
||||
{% with vars %}
|
||||
...
|
||||
|
||||
+12
-12
@@ -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
|
||||
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 method
|
||||
(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,
|
||||
``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, throw an exception.
|
||||
|
||||
@@ -531,10 +531,10 @@ exist:
|
||||
writing the number down. If a dot is present the number is a float,
|
||||
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 (``[]``).
|
||||
|
||||
* ``{"foo": "bar"}``: Hashes are defined by a list of keys and values
|
||||
* ``{"foo": "bar"}``: Mappings are defined by a list of keys and values
|
||||
separated by a comma (``,``) and wrapped with curly braces (``{}``):
|
||||
|
||||
.. code-block:: twig
|
||||
@@ -542,7 +542,7 @@ exist:
|
||||
{# keys as string #}
|
||||
{'foo': 'foo', 'bar': 'bar'}
|
||||
|
||||
{# keys as names (equivalent to the previous hash) #}
|
||||
{# keys as names (equivalent to the previous mapping) #}
|
||||
{foo: 'foo', bar: 'bar'}
|
||||
|
||||
{# keys as integer #}
|
||||
@@ -563,7 +563,7 @@ exist:
|
||||
* ``null``: ``null`` represents no specific value. This is the value returned
|
||||
when a variable does not exist. ``none`` is an alias for ``null``.
|
||||
|
||||
Arrays and hashes can be nested:
|
||||
Sequences and mappings can be nested:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -698,8 +698,8 @@ operand is contained in the right:
|
||||
|
||||
.. tip::
|
||||
|
||||
You can use this filter to perform a containment test on strings, arrays,
|
||||
or objects implementing the ``Traversable`` interface.
|
||||
You can use this filter to perform a containment test on strings,
|
||||
sequences, mappings, or objects implementing the ``Traversable`` interface.
|
||||
|
||||
To perform a negative test, use the ``not in`` operator:
|
||||
|
||||
@@ -785,8 +785,8 @@ 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 #}
|
||||
{{ foo ?? 'no' }}
|
||||
|
||||
* ``...``: The spread operator can be used to expand arrays or hashes (it cannot
|
||||
be used to expand the arguments of a function call):
|
||||
* ``...``: The spread operator can be used to expand sequences or mappings (it
|
||||
cannot be used to expand the arguments of a function call):
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -819,7 +819,7 @@ Operator Score of precedence Description
|
||||
``ends with``, ``has some``,
|
||||
``has every``
|
||||
``..`` 25 Range of values
|
||||
``+``, ``-`` 30 Addition and substraction on numbers
|
||||
``+``, ``-`` 30 Addition and subtraction on numbers
|
||||
``~`` 40 String concatenation
|
||||
``not`` 50 Negates a statement
|
||||
``*``, ``/``, ``//``, ``%`` 60 Arithmetic operations on numbers
|
||||
@@ -827,7 +827,7 @@ Operator Score of precedence Description
|
||||
``**`` 200 Raises a number to the power of another
|
||||
``??`` 300 Default value when a variable is null
|
||||
``+``, ``-`` 500 Unary operations on numbers
|
||||
``|``,``[]``,``.`` - Filters, array, hash, and attribute access
|
||||
``|``,``[]``,``.`` - Filters, sequence, mapping, and attribute access
|
||||
============================= =================================== =====================================================
|
||||
|
||||
Without using any parentheses, the operator precedence rules are used to
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
``empty``
|
||||
=========
|
||||
|
||||
``empty`` checks if a variable is an empty string, an empty array, an empty
|
||||
hash, exactly ``false``, or exactly ``null``.
|
||||
``empty`` checks if a variable is an empty string, an empty sequence, an empty
|
||||
mapping, exactly ``false``, or exactly ``null``.
|
||||
|
||||
For objects that implement the ``Countable`` interface, ``empty`` will check the
|
||||
return value of the ``count()`` method.
|
||||
|
||||
+29
-12
@@ -278,9 +278,9 @@ class ExpressionParser
|
||||
// no break
|
||||
default:
|
||||
if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
|
||||
$node = $this->parseArrayExpression();
|
||||
$node = $this->parseSequenceExpression();
|
||||
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
|
||||
$node = $this->parseHashExpression();
|
||||
$node = $this->parseMappingExpression();
|
||||
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
|
||||
throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
|
||||
} else {
|
||||
@@ -320,15 +320,22 @@ class ExpressionParser
|
||||
}
|
||||
|
||||
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->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());
|
||||
$first = true;
|
||||
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
|
||||
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 ,?
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
|
||||
@@ -346,21 +353,31 @@ class ExpressionParser
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 3.11, use parseMappingExpression() instead
|
||||
*/
|
||||
public function parseHashExpression()
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__);
|
||||
|
||||
return $this->parseMappingExpression();
|
||||
}
|
||||
|
||||
public function parseMappingExpression()
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A mapping element was expected');
|
||||
|
||||
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
|
||||
$first = true;
|
||||
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
|
||||
if (!$first) {
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A mapping value must be followed by a comma');
|
||||
|
||||
// trailing ,?
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
|
||||
@@ -377,7 +394,7 @@ class ExpressionParser
|
||||
continue;
|
||||
}
|
||||
|
||||
// a hash key can be:
|
||||
// a mapping key can be:
|
||||
//
|
||||
// * a number -- 12
|
||||
// * a string -- 'a'
|
||||
@@ -399,15 +416,15 @@ class ExpressionParser
|
||||
} else {
|
||||
$current = $stream->getCurrent();
|
||||
|
||||
throw new SyntaxError(\sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
|
||||
throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A mapping key must be followed by a colon (:)');
|
||||
$value = $this->parseExpression();
|
||||
|
||||
$node->addElement($value, $key);
|
||||
}
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened mapping is not properly closed');
|
||||
|
||||
return $node;
|
||||
}
|
||||
@@ -631,7 +648,7 @@ class ExpressionParser
|
||||
$value = $this->parsePrimaryExpression();
|
||||
|
||||
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 {
|
||||
$value = $this->parseExpression(0, $allowArrow);
|
||||
|
||||
@@ -329,7 +329,7 @@ final class CoreExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
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)];
|
||||
@@ -399,7 +399,7 @@ final class CoreExtension extends AbstractExtension
|
||||
$values = self::toArray($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)];
|
||||
@@ -532,7 +532,7 @@ final class CoreExtension extends AbstractExtension
|
||||
public static function replace($str, $from): string
|
||||
{
|
||||
if (!is_iterable($from)) {
|
||||
throw new RuntimeError(\sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', get_debug_type($from)));
|
||||
throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', get_debug_type($from)));
|
||||
}
|
||||
|
||||
return strtr($str ?? '', self::toArray($from));
|
||||
@@ -629,7 +629,7 @@ final class CoreExtension extends AbstractExtension
|
||||
|
||||
foreach ($arrays as $argNumber => $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));
|
||||
@@ -903,7 +903,7 @@ final class CoreExtension extends AbstractExtension
|
||||
if ($array instanceof \Traversable) {
|
||||
$array = iterator_to_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) {
|
||||
@@ -1416,7 +1416,7 @@ final class CoreExtension extends AbstractExtension
|
||||
public static function batch($items, $size, $fill = null, $preserveKeys = true): array
|
||||
{
|
||||
if (!is_iterable($items)) {
|
||||
throw new RuntimeError(\sprintf('The "batch" filter expects an array or "Traversable", got "%s".', get_debug_type($items)));
|
||||
throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($items)));
|
||||
}
|
||||
|
||||
$size = ceil($size);
|
||||
@@ -1483,9 +1483,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, $object::class);
|
||||
} elseif (\is_array($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 {
|
||||
$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) {
|
||||
if (null === $object) {
|
||||
@@ -1515,7 +1515,7 @@ final class CoreExtension extends AbstractExtension
|
||||
if (null === $object) {
|
||||
$message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item);
|
||||
} 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 {
|
||||
$message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
|
||||
}
|
||||
@@ -1653,7 +1653,7 @@ final class CoreExtension extends AbstractExtension
|
||||
if ($array instanceof \Traversable) {
|
||||
$array = iterator_to_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);
|
||||
@@ -1665,7 +1665,7 @@ final class CoreExtension extends AbstractExtension
|
||||
public static function filter(Environment $env, $array, $arrow)
|
||||
{
|
||||
if (!is_iterable($array)) {
|
||||
throw new RuntimeError(\sprintf('The "filter" filter expects an array or "Traversable", got "%s".', get_debug_type($array)));
|
||||
throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array)));
|
||||
}
|
||||
|
||||
self::checkArrowInSandbox($env, $arrow, 'filter', 'filter');
|
||||
@@ -1701,7 +1701,7 @@ final class CoreExtension extends AbstractExtension
|
||||
self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter');
|
||||
|
||||
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;
|
||||
|
||||
@@ -49,7 +49,7 @@ class WithNode extends Node
|
||||
->raw(";\n")
|
||||
->write(\sprintf("if (!is_iterable(\$%s)) {\n", $varsName))
|
||||
->indent()
|
||||
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ")
|
||||
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a mapping.', ")
|
||||
->repr($node->getTemplateLine())
|
||||
->raw(", \$this->getSourceContext());\n")
|
||||
->outdent()
|
||||
|
||||
@@ -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]);
|
||||
$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);
|
||||
|
||||
@@ -79,7 +79,7 @@ class ExpressionParserTest extends TestCase
|
||||
$parser->parse($env->tokenize(new Source($template, 'index')));
|
||||
}
|
||||
|
||||
public function getFailingTestsForArray()
|
||||
public function getFailingTestsForSequence()
|
||||
{
|
||||
return [
|
||||
['{{ [1, "a": "b"] }}'],
|
||||
@@ -88,10 +88,10 @@ class ExpressionParserTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function getTestsForArray()
|
||||
public function getTestsForSequence()
|
||||
{
|
||||
return [
|
||||
// simple array
|
||||
// simple sequence
|
||||
['{{ [1, 2] }}', new ArrayExpression([
|
||||
new ConstantExpression(0, 1),
|
||||
new ConstantExpression(1, 1),
|
||||
@@ -101,7 +101,7 @@ class ExpressionParserTest extends TestCase
|
||||
], 1),
|
||||
],
|
||||
|
||||
// array with trailing ,
|
||||
// sequence with trailing ,
|
||||
['{{ [1, 2, ] }}', new ArrayExpression([
|
||||
new ConstantExpression(0, 1),
|
||||
new ConstantExpression(1, 1),
|
||||
@@ -111,7 +111,7 @@ class ExpressionParserTest extends TestCase
|
||||
], 1),
|
||||
],
|
||||
|
||||
// simple hash
|
||||
// simple mapping
|
||||
['{{ {"a": "b", "b": "c"} }}', new ArrayExpression([
|
||||
new ConstantExpression('a', 1),
|
||||
new ConstantExpression('b', 1),
|
||||
@@ -121,7 +121,7 @@ class ExpressionParserTest extends TestCase
|
||||
], 1),
|
||||
],
|
||||
|
||||
// hash with trailing ,
|
||||
// mapping with trailing ,
|
||||
['{{ {"a": "b", "b": "c", } }}', new ArrayExpression([
|
||||
new ConstantExpression('a', 1),
|
||||
new ConstantExpression('b', 1),
|
||||
@@ -131,7 +131,7 @@ class ExpressionParserTest extends TestCase
|
||||
], 1),
|
||||
],
|
||||
|
||||
// hash in an array
|
||||
// mapping in a sequence
|
||||
['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([
|
||||
new ConstantExpression(0, 1),
|
||||
new ConstantExpression(1, 1),
|
||||
@@ -147,7 +147,7 @@ class ExpressionParserTest extends TestCase
|
||||
], 1),
|
||||
],
|
||||
|
||||
// array in a hash
|
||||
// sequence in a mapping
|
||||
['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([
|
||||
new ConstantExpression('a', 1),
|
||||
new ArrayExpression([
|
||||
@@ -168,7 +168,7 @@ class ExpressionParserTest extends TestCase
|
||||
new NameExpression('b', 1),
|
||||
], 1)],
|
||||
|
||||
// array with spread operator
|
||||
// sequence with spread operator
|
||||
['{{ [1, 2, ...foo] }}',
|
||||
new ArrayExpression([
|
||||
new ConstantExpression(0, 1),
|
||||
@@ -181,7 +181,7 @@ class ExpressionParserTest extends TestCase
|
||||
$this->createNameExpression('foo', ['spread' => true]),
|
||||
], 1)],
|
||||
|
||||
// hash with spread operator
|
||||
// mapping with spread operator
|
||||
['{{ {"a": "b", "b": "c", ...otherLetters} }}',
|
||||
new ArrayExpression([
|
||||
new ConstantExpression('a', 1),
|
||||
@@ -301,7 +301,7 @@ class ExpressionParserTest extends TestCase
|
||||
public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template)
|
||||
{
|
||||
$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]);
|
||||
$parser = new Parser($env);
|
||||
|
||||
@@ -9,4 +9,4 @@ Exception thrown from a child for an extension error
|
||||
--DATA--
|
||||
return []
|
||||
--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--
|
||||
return []
|
||||
--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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
--TEST--
|
||||
Twig supports the spread operator on hashes
|
||||
Twig supports the spread operator on mappings
|
||||
--TEMPLATE--
|
||||
{% for key, value in { firstName: 'Ryan', lastName: 'Weaver', favoriteFood: 'popcorn', ...{favoriteFood: 'pizza', sport: 'running'} } %}
|
||||
{{ key }}: {{ value }}
|
||||
@@ -5,4 +5,4 @@ Exception for invalid argument type in replace call
|
||||
--DATA--
|
||||
return ['stdClass' => new \stdClass()]
|
||||
--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.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
--TEST--
|
||||
"cycle" function returns an error on empty mappings
|
||||
--TEMPLATE--
|
||||
{{ cycle({}, 0) }}
|
||||
--DATA--
|
||||
return []
|
||||
--EXCEPTION--
|
||||
Twig\Error\RuntimeError: The "cycle" function does not work on empty sequences/mappings in "index.twig" at line 2.
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
--TEST--
|
||||
"cycle" function returns an error on empty arrays
|
||||
"cycle" function returns an error on empty sequences
|
||||
--TEMPLATE--
|
||||
{{ cycle([], 0) }}
|
||||
--DATA--
|
||||
return []
|
||||
--EXCEPTION--
|
||||
Twig\Error\RuntimeError: The "cycle" function does not work on empty arrays in "index.twig" at line 2.
|
||||
Twig\Error\RuntimeError: The "cycle" function does not work on empty sequences/mappings in "index.twig" at line 2.
|
||||
@@ -1,8 +1,8 @@
|
||||
--TEST--
|
||||
Twig does not confuse strings with integers in getAttribute()
|
||||
--TEMPLATE--
|
||||
{{ hash['2e2'] }}
|
||||
{{ mapping['2e2'] }}
|
||||
--DATA--
|
||||
return ['hash' => ['2e2' => 'works']]
|
||||
return ['mapping' => ['2e2' => 'works']]
|
||||
--EXPECT--
|
||||
works
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
--TEST--
|
||||
"with" tag with an expression that is not a hash
|
||||
"with" tag with an expression that is not a mapping
|
||||
--TEMPLATE--
|
||||
{% with vars %}
|
||||
{{ foo }}{{ bar }}
|
||||
{% endwith %}
|
||||
--DATA--
|
||||
return ['vars' => 'no-hash']
|
||||
return ['vars' => 'no-mapping']
|
||||
--EXCEPTION--
|
||||
Twig\Error\RuntimeError: Variables passed to the "with" tag must be a hash in "index.twig" at line 2.
|
||||
Twig\Error\RuntimeError: Variables passed to the "with" tag must be a mapping in "index.twig" at line 2.
|
||||
+15
-15
@@ -66,17 +66,17 @@ class TemplateTest extends TestCase
|
||||
return [
|
||||
['{{ 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.'],
|
||||
['{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'],
|
||||
['{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist 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 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.'],
|
||||
['{{ 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.'],
|
||||
['{{ 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.'],
|
||||
['{{ array.a() }}', 'Impossible to invoke a method ("a") on an array in "%s" at line 1.'],
|
||||
['{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'],
|
||||
['{{ array.a }}', 'Key "a" for array 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.'],
|
||||
['{{ 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 sequence/mapping is empty 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 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.'],
|
||||
['{% 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.'],
|
||||
@@ -175,14 +175,14 @@ class TemplateTest extends TestCase
|
||||
$this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);
|
||||
$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('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, 1.5), 'float is casted to int 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 an array (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('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" 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 an array (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('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 a sequence/mapping (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 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 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 a sequence/mapping (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 a sequence/mapping (equals PHP behavior)');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,7 +377,7 @@ class TemplateTest extends TestCase
|
||||
$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, '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;
|
||||
|
||||
Reference in New Issue
Block a user