From 5e9448310d6650254e411022c46eee7f820757aa Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 4 Oct 2024 18:48:30 +0200 Subject: [PATCH] Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a Closure) --- CHANGELOG | 1 + doc/deprecated.rst | 4 ++++ src/Extension/CoreExtension.php | 37 +++++++++++++++++++++++++-------- src/Resources/core.php | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index eb273f399f9..dda9d5191bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.15.0 (2024-XX-XX) + * Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a `\Closure`) * Add support for triggering deprecations for future operator precedence changes * Deprecate using the `not` unary operator in an expression with ``*``, ``/``, ``//``, or ``%`` without using explicit parentheses to clarify precedence * Deprecate using the `??` binary operator without explicit parentheses diff --git a/doc/deprecated.rst b/doc/deprecated.rst index e1967addaae..1239c77bcea 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -290,6 +290,10 @@ Functions/Filters/Tests * For variadic arguments, use snake-case for the argument name to ease the transition to 4.0. +* Passing a ``string`` or an ``array`` to Twig callable arguments accepting + arrow functions is deprecated as of Twig 3.15; these arguments will have a + ``\Closure`` type hint in 4.0. + Node ---- diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 89cbbaa6f8b..334ab408495 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -972,6 +972,7 @@ public static function shuffle(string $charset, $item) * Sorts an array. * * @param array|\Traversable $array + * @param ?\Closure $arrow * * @internal */ @@ -984,7 +985,7 @@ public static function sort(Environment $env, $array, $arrow = null): array } if (null !== $arrow) { - self::checkArrowInSandbox($env, $arrow, 'sort', 'filter'); + self::checkArrow($env, $arrow, 'sort', 'filter'); uasort($array, $arrow); } else { @@ -1838,6 +1839,8 @@ public static function column($array, $name, $index = null): array } /** + * @param \Closure $arrow + * * @internal */ public static function filter(Environment $env, $array, $arrow) @@ -1846,7 +1849,7 @@ public static function filter(Environment $env, $array, $arrow) 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'); + self::checkArrow($env, $arrow, 'filter', 'filter'); if (\is_array($array)) { return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); @@ -1857,6 +1860,8 @@ public static function filter(Environment $env, $array, $arrow) } /** + * @param \Closure $arrow + * * @internal */ public static function find(Environment $env, $array, $arrow) @@ -1865,7 +1870,7 @@ public static function find(Environment $env, $array, $arrow) throw new RuntimeError(\sprintf('The "find" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'find', 'filter'); + self::checkArrow($env, $arrow, 'find', 'filter'); foreach ($array as $k => $v) { if ($arrow($v, $k)) { @@ -1877,6 +1882,8 @@ public static function find(Environment $env, $array, $arrow) } /** + * @param \Closure $arrow + * * @internal */ public static function map(Environment $env, $array, $arrow) @@ -1885,7 +1892,7 @@ public static function map(Environment $env, $array, $arrow) throw new RuntimeError(\sprintf('The "map" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'map', 'filter'); + self::checkArrow($env, $arrow, 'map', 'filter'); $r = []; foreach ($array as $k => $v) { @@ -1896,6 +1903,8 @@ public static function map(Environment $env, $array, $arrow) } /** + * @param \Closure $arrow + * * @internal */ public static function reduce(Environment $env, $array, $arrow, $initial = null) @@ -1904,7 +1913,7 @@ public static function reduce(Environment $env, $array, $arrow, $initial = null) throw new RuntimeError(\sprintf('The "reduce" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); + self::checkArrow($env, $arrow, 'reduce', 'filter'); $accumulator = $initial; foreach ($array as $key => $value) { @@ -1915,6 +1924,8 @@ public static function reduce(Environment $env, $array, $arrow, $initial = null) } /** + * @param \Closure $arrow + * * @internal */ public static function arraySome(Environment $env, $array, $arrow) @@ -1923,7 +1934,7 @@ public static function arraySome(Environment $env, $array, $arrow) throw new RuntimeError(\sprintf('The "has some" test expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'has some', 'operator'); + self::checkArrow($env, $arrow, 'has some', 'operator'); foreach ($array as $k => $v) { if ($arrow($v, $k)) { @@ -1935,6 +1946,8 @@ public static function arraySome(Environment $env, $array, $arrow) } /** + * @param \Closure $arrow + * * @internal */ public static function arrayEvery(Environment $env, $array, $arrow) @@ -1943,7 +1956,7 @@ public static function arrayEvery(Environment $env, $array, $arrow) throw new RuntimeError(\sprintf('The "has every" test expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'has every', 'operator'); + self::checkArrow($env, $arrow, 'has every', 'operator'); foreach ($array as $k => $v) { if (!$arrow($v, $k)) { @@ -1957,11 +1970,17 @@ public static function arrayEvery(Environment $env, $array, $arrow) /** * @internal */ - public static function checkArrowInSandbox(Environment $env, $arrow, $thing, $type) + public static function checkArrow(Environment $env, $arrow, $thing, $type) { - if (!$arrow instanceof \Closure && $env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) { + if ($arrow instanceof \Closure) { + return; + } + + if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) { throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); } + + trigger_deprecation('twig/twig', '3.15', 'Passing a callable that is not a PHP \Closure as an argument to the "%s" %s is deprecated.', $thing, $type); } /** diff --git a/src/Resources/core.php b/src/Resources/core.php index 6e2fcfb0758..bc0b27104b0 100644 --- a/src/Resources/core.php +++ b/src/Resources/core.php @@ -537,5 +537,5 @@ function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::checkArrowInSandbox($env, $arrow, $thing, $type); + CoreExtension::checkArrow($env, $arrow, $thing, $type); }