diff --git a/CHANGELOG b/CHANGELOG index b86b9e5fc08..46134b232d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,20 @@ +# 3.6.0 (2023-05-03) + + * Allow psr/container 2.0 + * Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting + * Make the Lexer initialize itself lazily + +# 3.5.1 (2023-02-08) + + * Arrow functions passed to the "reduce" filter now accept the current key as a third argument + * Restores the leniency of the matches twig comparison + * Fix error messages in sandboxed mode for "has some" and "has every" + # 3.5.0 (2022-12-27) - * Make Twig\ExpressionParser non internal - * Add "some" and "every" filters - * Add Compile::reset( + * Make Twig\ExpressionParser non-internal + * Add "has some" and "has every" operators + * Add Compile::reset() * Throw a better runtime error when the "matches" regexp is not valid * Add "twig *_names" intl functions * Fix optimizing closures callbacks diff --git a/LICENSE b/LICENSE index 8711927f6d9..fd8234e511b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2022 by the Twig Team. +Copyright (c) 2009-present by the Twig Team. All rights reserved. diff --git a/composer.json b/composer.json index 18d3135bb4b..aeea64053ff 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ }, "require-dev": { "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", - "psr/container": "^1.0" + "psr/container": "^1.0|^2.0" }, "autoload": { "psr-4" : { @@ -41,10 +41,5 @@ "psr-4" : { "Twig\\Tests\\" : "tests/" } - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/doc/filters/format_date.rst b/doc/filters/format_date.rst index c4a900a4360..cd6beba9f5b 100644 --- a/doc/filters/format_date.rst +++ b/doc/filters/format_date.rst @@ -33,4 +33,4 @@ Arguments * ``dateFormat``: The date format * ``pattern``: A date time pattern * ``timezone``: The date timezone -* ``calendar``: The calendar (Gregorian by default) +* ``calendar``: The calendar ("gregorian" by default) diff --git a/doc/filters/format_datetime.rst b/doc/filters/format_datetime.rst index 9fed54f8ec9..8f3b46d479a 100644 --- a/doc/filters/format_datetime.rst +++ b/doc/filters/format_datetime.rst @@ -26,6 +26,12 @@ You can tweak the output for the date part and the time part: Supported values are: ``none``, ``short``, ``medium``, ``long``, and ``full``. +.. versionadded:: 3.6 + + ``relative_short``, ``relative_medium``, ``relative_long``, and ``relative_full`` are also supported when running on + PHP 8.0 and superior or when using a polyfill that define the ``IntlDateFormatter::RELATIVE_*`` constants and + associated behavior. + For greater flexibility, you can even define your own pattern (see the `ICU user guide`_ for supported patterns). @@ -97,6 +103,6 @@ Arguments * ``timeFormat``: The time format * ``pattern``: A date time pattern * ``timezone``: The date timezone name -* ``calendar``: The calendar (Gregorian by default) +* ``calendar``: The calendar ("gregorian" by default) .. _ICU user guide: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax diff --git a/doc/filters/format_time.rst b/doc/filters/format_time.rst index 417b8a9c62b..1e213e6163b 100644 --- a/doc/filters/format_time.rst +++ b/doc/filters/format_time.rst @@ -33,4 +33,4 @@ Arguments * ``timeFormat``: The time format * ``pattern``: A date time pattern * ``timezone``: The date timezone -* ``calendar``: The calendar (Gregorian by default) +* ``calendar``: The calendar ("gregorian" by default) diff --git a/doc/filters/length.rst b/doc/filters/length.rst index d36712233cd..a9dfae423ca 100644 --- a/doc/filters/length.rst +++ b/doc/filters/length.rst @@ -12,8 +12,12 @@ it will return the length of the string provided by that method. For objects that implement the ``Traversable`` interface, ``length`` will use the return value of the ``iterator_count()`` method. +For strings, `mb_strlen()`_ is used. + .. code-block:: twig {% if users|length > 10 %} ... {% endif %} + +.. _mb_strlen(): https://www.php.net/manual/function.mb-strlen.php diff --git a/doc/filters/reduce.rst b/doc/filters/reduce.rst index 7df4646c745..72c68d0deb9 100644 --- a/doc/filters/reduce.rst +++ b/doc/filters/reduce.rst @@ -4,21 +4,21 @@ The ``reduce`` filter iteratively reduces a sequence or a mapping to a single value using an arrow function, so as to reduce it to a single value. The arrow function receives the return value of the previous iteration and the current -value of the sequence or mapping: +value and key of the sequence or mapping: .. code-block:: twig {% set numbers = [1, 2, 3] %} - {{ numbers|reduce((carry, v) => carry + v) }} - {# output 6 #} + {{ numbers|reduce((carry, v, k) => carry + v * k) }} + {# output 8 #} The ``reduce`` filter takes an ``initial`` value as a second argument: .. code-block:: twig - {{ numbers|reduce((carry, v) => carry + v, 10) }} - {# output 16 #} + {{ numbers|reduce((carry, v, k) => carry + v * k, 10) }} + {# output 18 #} Note that the arrow function has access to the current context. diff --git a/doc/filters/striptags.rst b/doc/filters/striptags.rst index d5f542b3d8b..64a9c8156fe 100644 --- a/doc/filters/striptags.rst +++ b/doc/filters/striptags.rst @@ -1,7 +1,7 @@ ``striptags`` ============= -The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace +The ``striptags`` filter strips SGML/XML tags and replaces adjacent whitespace characters by one space: .. code-block:: twig diff --git a/doc/functions/index.rst b/doc/functions/index.rst index 97465ed0395..eebcd61c7ab 100644 --- a/doc/functions/index.rst +++ b/doc/functions/index.rst @@ -19,4 +19,10 @@ Functions range source country_timezones + country_names + currency_names + language_names + locale_names + script_names + timezone_names template_from_string diff --git a/doc/tags/if.rst b/doc/tags/if.rst index 2d7475227c1..0523cb1ac19 100644 --- a/doc/tags/if.rst +++ b/doc/tags/if.rst @@ -26,8 +26,7 @@ You can also test if an array is not empty: .. note:: - If you want to test if the variable is defined, use ``if users is - defined`` instead. + If you want to test if the variable is defined, use ``if users is defined`` instead. You can also use ``not`` to check for values that evaluate to ``false``: diff --git a/doc/templates.rst b/doc/templates.rst index 380d42b4f37..5e52326e442 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -505,7 +505,8 @@ Twig allows expressions everywhere. The operator precedence is as follows, with the lowest-precedence operators listed first: ``?:`` (ternary operator), ``b-and``, ``b-xor``, ``b-or``, ``or``, ``and``, ``==``, ``!=``, ``<=>``, ``<``, ``>``, ``>=``, ``<=``, - ``in``, ``matches``, ``starts with``, ``ends with``, ``..``, ``+``, ``-``, + ``in``, ``matches``, ``starts with``, ``ends with``, ``has every``, ``has + some``, ``..``, ``+``, ``-``, ``~``, ``*``, ``/``, ``//``, ``%``, ``is`` (tests), ``**``, ``??``, ``|`` (filters), ``[]``, and ``.``: @@ -661,6 +662,20 @@ next section). {% if phone matches '/^[\\d\\.]+$/' %} {% endif %} +Check that a sequence or a mapping ``has every`` or ``has some`` of its +elements return ``true`` using an arrow function. The arrow function receives +the value of the sequence or mapping: + +.. code-block:: twig + + {% set sizes = [34, 36, 38, 40, 42] %} + + {% set hasOnlyOver38 = sizes has every v => v > 38 %} + {# hasOnlyOver38 is false #} + + {% set hasOver38 = sizes has some v => v > 38 %} + {# hasOver38 is true #} + Containment Operator ~~~~~~~~~~~~~~~~~~~~ diff --git a/extra/cache-extra/LICENSE b/extra/cache-extra/LICENSE index efb17f98e7d..99c6bdf356e 100644 --- a/extra/cache-extra/LICENSE +++ b/extra/cache-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 Fabien Potencier +Copyright (c) 2021-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/cache-extra/composer.json b/extra/cache-extra/composer.json index 05d75afd5f4..ec116d7d4ab 100644 --- a/extra/cache-extra/composer.json +++ b/extra/cache-extra/composer.json @@ -27,10 +27,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/cssinliner-extra/LICENSE b/extra/cssinliner-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/cssinliner-extra/LICENSE +++ b/extra/cssinliner-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/cssinliner-extra/composer.json b/extra/cssinliner-extra/composer.json index 1866f417ef9..a6799ed0dce 100644 --- a/extra/cssinliner-extra/composer.json +++ b/extra/cssinliner-extra/composer.json @@ -27,10 +27,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/html-extra/LICENSE b/extra/html-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/html-extra/LICENSE +++ b/extra/html-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/html-extra/composer.json b/extra/html-extra/composer.json index e5a545cf086..44f5a9faa46 100644 --- a/extra/html-extra/composer.json +++ b/extra/html-extra/composer.json @@ -27,10 +27,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/inky-extra/LICENSE b/extra/inky-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/inky-extra/LICENSE +++ b/extra/inky-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/inky-extra/composer.json b/extra/inky-extra/composer.json index 0a813c7bf2a..17e1be92c96 100644 --- a/extra/inky-extra/composer.json +++ b/extra/inky-extra/composer.json @@ -27,10 +27,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/intl-extra/IntlExtension.php b/extra/intl-extra/IntlExtension.php index 76c2b271e71..955d6ec9233 100644 --- a/extra/intl-extra/IntlExtension.php +++ b/extra/intl-extra/IntlExtension.php @@ -26,7 +26,36 @@ final class IntlExtension extends AbstractExtension { - private const DATE_FORMATS = [ + private static function availableDateFormats(): array + { + static $formats = null; + + if (null !== $formats) { + return $formats; + } + + $formats = [ + 'none' => \IntlDateFormatter::NONE, + 'short' => \IntlDateFormatter::SHORT, + 'medium' => \IntlDateFormatter::MEDIUM, + 'long' => \IntlDateFormatter::LONG, + 'full' => \IntlDateFormatter::FULL, + ]; + + // Assuming that each `RELATIVE_*` constant are defined when one of them is. + if (\defined('IntlDateFormatter::RELATIVE_FULL')) { + $formats = array_merge($formats, [ + 'relative_short' => \IntlDateFormatter::RELATIVE_SHORT, + 'relative_medium' => \IntlDateFormatter::RELATIVE_MEDIUM, + 'relative_long' => \IntlDateFormatter::RELATIVE_LONG, + 'relative_full' => \IntlDateFormatter::RELATIVE_FULL, + ]); + } + + return $formats; + } + + private const TIME_FORMATS = [ 'none' => \IntlDateFormatter::NONE, 'short' => \IntlDateFormatter::SHORT, 'medium' => \IntlDateFormatter::MEDIUM, @@ -370,12 +399,14 @@ public function formatTime(Environment $env, $date, ?string $timeFormat = 'mediu private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern, \DateTimeZone $timezone, string $calendar): \IntlDateFormatter { - if (null !== $dateFormat && !isset(self::DATE_FORMATS[$dateFormat])) { - throw new RuntimeError(sprintf('The date format "%s" does not exist, known formats are: "%s".', $dateFormat, implode('", "', array_keys(self::DATE_FORMATS)))); + $dateFormats = self::availableDateFormats(); + + if (null !== $dateFormat && !isset($dateFormats[$dateFormat])) { + throw new RuntimeError(sprintf('The date format "%s" does not exist, known formats are: "%s".', $dateFormat, implode('", "', array_keys($dateFormats)))); } - if (null !== $timeFormat && !isset(self::DATE_FORMATS[$timeFormat])) { - throw new RuntimeError(sprintf('The time format "%s" does not exist, known formats are: "%s".', $timeFormat, implode('", "', array_keys(self::DATE_FORMATS)))); + if (null !== $timeFormat && !isset(self::TIME_FORMATS[$timeFormat])) { + throw new RuntimeError(sprintf('The time format "%s" does not exist, known formats are: "%s".', $timeFormat, implode('", "', array_keys(self::TIME_FORMATS)))); } if (null === $locale) { @@ -384,8 +415,8 @@ private function createDateFormatter(?string $locale, ?string $dateFormat, ?stri $calendar = 'gregorian' === $calendar ? \IntlDateFormatter::GREGORIAN : \IntlDateFormatter::TRADITIONAL; - $dateFormatValue = self::DATE_FORMATS[$dateFormat] ?? null; - $timeFormatValue = self::DATE_FORMATS[$timeFormat] ?? null; + $dateFormatValue = $dateFormats[$dateFormat] ?? null; + $timeFormatValue = self::TIME_FORMATS[$timeFormat] ?? null; if ($this->dateFormatterPrototype) { $dateFormatValue = $dateFormatValue ?: $this->dateFormatterPrototype->getDateType(); diff --git a/extra/intl-extra/LICENSE b/extra/intl-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/intl-extra/LICENSE +++ b/extra/intl-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/intl-extra/Tests/Fixtures/format_date_php8.test b/extra/intl-extra/Tests/Fixtures/format_date_php8.test new file mode 100644 index 00000000000..67e0e6f4dbe --- /dev/null +++ b/extra/intl-extra/Tests/Fixtures/format_date_php8.test @@ -0,0 +1,12 @@ +--TEST-- +"format_date" filter +--CONDITION-- +PHP_VERSION_ID >= 80000 +--TEMPLATE-- +{{ 'today 23:39:12'|format_datetime('relative_short', 'none', locale='fr') }} +{{ 'today 23:39:12'|format_datetime('relative_full', 'full', locale='fr') }} +--DATA-- +return []; +--EXPECT-- +aujourd’hui +aujourd’hui à 23:39:12 temps universel coordonné diff --git a/extra/intl-extra/composer.json b/extra/intl-extra/composer.json index 24f3f0577e3..d357ad7254e 100644 --- a/extra/intl-extra/composer.json +++ b/extra/intl-extra/composer.json @@ -27,10 +27,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/markdown-extra/LICENSE b/extra/markdown-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/markdown-extra/LICENSE +++ b/extra/markdown-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/markdown-extra/composer.json b/extra/markdown-extra/composer.json index f26143525a8..20389ff9b24 100644 --- a/extra/markdown-extra/composer.json +++ b/extra/markdown-extra/composer.json @@ -30,10 +30,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/string-extra/LICENSE b/extra/string-extra/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/string-extra/LICENSE +++ b/extra/string-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/string-extra/composer.json b/extra/string-extra/composer.json index 2b4a6950e6e..ed5d3ffa2f1 100644 --- a/extra/string-extra/composer.json +++ b/extra/string-extra/composer.json @@ -28,10 +28,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/extra/twig-extra-bundle/LICENSE b/extra/twig-extra-bundle/LICENSE index 9c907a46a62..f37c76b591d 100644 --- a/extra/twig-extra-bundle/LICENSE +++ b/extra/twig-extra-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022 Fabien Potencier +Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/extra/twig-extra-bundle/TwigExtraBundle.php b/extra/twig-extra-bundle/TwigExtraBundle.php index 5ed32a8ccca..a9c8f734bf8 100644 --- a/extra/twig-extra-bundle/TwigExtraBundle.php +++ b/extra/twig-extra-bundle/TwigExtraBundle.php @@ -17,6 +17,7 @@ class TwigExtraBundle extends Bundle { + /** @return void */ public function build(ContainerBuilder $container) { parent::build($container); diff --git a/extra/twig-extra-bundle/composer.json b/extra/twig-extra-bundle/composer.json index 3e183704493..b7f739bdf1f 100644 --- a/extra/twig-extra-bundle/composer.json +++ b/extra/twig-extra-bundle/composer.json @@ -36,10 +36,5 @@ "exclude-from-classmap": [ "/Tests/" ] - }, - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } } } diff --git a/src/Environment.php b/src/Environment.php index c4f2885849d..4b7d989bbef 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -40,10 +40,10 @@ */ class Environment { - public const VERSION = '3.5.0'; - public const VERSION_ID = 30500; + public const VERSION = '3.6.0'; + public const VERSION_ID = 30600; public const MAJOR_VERSION = 3; - public const MINOR_VERSION = 5; + public const MINOR_VERSION = 3; public const RELEASE_VERSION = 0; public const EXTRA_VERSION = ''; diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 65caab31fe1..f99adda451b 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -1021,19 +1021,19 @@ function twig_compare($a, $b) /** * @param string $pattern - * @param string $subject + * @param string|null $subject * * @return int * * @throws RuntimeError When an invalid pattern is used */ -function twig_matches(string $regexp, string $str) +function twig_matches(string $regexp, ?string $str) { set_error_handler(function ($t, $m) use ($regexp) { throw new RuntimeError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); }); try { - return preg_match($regexp, $str); + return preg_match($regexp, $str ?? ''); } finally { restore_error_handler(); } @@ -1703,20 +1703,21 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); - if (!\is_array($array)) { - if (!$array instanceof \Traversable) { - throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); - } + 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))); + } - $array = iterator_to_array($array); + $accumulator = $initial; + foreach ($array as $key => $value) { + $accumulator = $arrow($accumulator, $value, $key); } - return array_reduce($array, $arrow, $initial); + return $accumulator; } function twig_array_some(Environment $env, $array, $arrow) { - twig_check_arrow_in_sandbox($env, $arrow, 'some', 'filter'); + twig_check_arrow_in_sandbox($env, $arrow, 'has some', 'operator'); foreach ($array as $k => $v) { if ($arrow($v, $k)) { @@ -1729,7 +1730,7 @@ function twig_array_some(Environment $env, $array, $arrow) function twig_array_every(Environment $env, $array, $arrow) { - twig_check_arrow_in_sandbox($env, $arrow, 'every', 'filter'); + twig_check_arrow_in_sandbox($env, $arrow, 'has every', 'operator'); foreach ($array as $k => $v) { if (!$arrow($v, $k)) { diff --git a/src/Lexer.php b/src/Lexer.php index 9ff028c87d9..975b0b924ff 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -19,6 +19,8 @@ */ class Lexer { + private $isInitialized = false; + private $tokens; private $code; private $cursor; @@ -61,6 +63,15 @@ public function __construct(Environment $env, array $options = []) 'whitespace_line_chars' => ' \t\0\x0B', 'interpolation' => ['#{', '}'], ], $options); + } + + private function initialize() + { + if ($this->isInitialized) { + return; + } + + $this->isInitialized = true; // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default $this->regexes = [ @@ -153,6 +164,8 @@ public function __construct(Environment $env, array $options = []) public function tokenize(Source $source): TokenStream { + $this->initialize(); + $this->source = $source; $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); $this->cursor = 0; diff --git a/tests/Fixtures/expressions/matches.test b/tests/Fixtures/expressions/matches.test index 95459c3b0f2..8f5e3669e61 100644 --- a/tests/Fixtures/expressions/matches.test +++ b/tests/Fixtures/expressions/matches.test @@ -4,9 +4,11 @@ Twig supports the "matches" operator {{ 'foo' matches '/o/' ? 'OK' : 'KO' }} {{ 'foo' matches '/^fo/' ? 'OK' : 'KO' }} {{ 'foo' matches '/O/i' ? 'OK' : 'KO' }} +{{ null matches '/o/' }} --DATA-- return [] --EXPECT-- OK OK OK +0 diff --git a/tests/Fixtures/filters/reduce_key.test b/tests/Fixtures/filters/reduce_key.test new file mode 100644 index 00000000000..fe1fb0a7ac5 --- /dev/null +++ b/tests/Fixtures/filters/reduce_key.test @@ -0,0 +1,14 @@ +--TEST-- +"reduce" filter passes iterable key to callback +--TEMPLATE-- +{% set status_classes = { + 'success': 200, + 'warning': 400, + 'error': 500, +} %} + +{{ status_classes|reduce((carry, v, k) => status_code >= v ? k : carry, '') }} +--DATA-- +return ['status_code' => 404] +--EXPECT-- +warning