Merge branch '3.x' into 4.x

* 3.x:
  use getShareDir as an indicator of Symfony version
  Add 'invoke' to filters index
  [Core] Fix cycle() with non-countable ArrayAccess+Traversable objects
  Update .gitattributes to exclude phpstan configs
  Bump version
  Prepare the 3.22.1 release
  Update CHANGELOG
  `CoreExtension` : Make error message more readable
  Add caution note for random function usage
  Add throw tag to parse methods
This commit is contained in:
Fabien Potencier
2025-12-05 09:58:08 +01:00
9 changed files with 27 additions and 5 deletions
+2
View File
@@ -8,3 +8,5 @@
/.gitignore export-ignore
/.php-cs-fixer.dist.php export-ignore
/phpunit.xml.dist export-ignore
/phpstan.neon.dist export-ignore
/phpstan-baseline.neon export-ignore
+1
View File
@@ -29,6 +29,7 @@ Filters
html_to_markdown
inline_css
inky_to_html
invoke
join
json_encode
keys
+5
View File
@@ -10,6 +10,11 @@ parameter type:
* a random integer between the integer parameter (when negative) and 0 (inclusive).
* a random integer between the first integer and the second integer parameter (inclusive).
.. caution::
The ``random`` function does not produce cryptographically secure random numbers.
Do not use them for purposes that require returned values to be unguessable.
.. code-block:: twig
{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
@@ -11,12 +11,16 @@
namespace Twig\ExpressionParser;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Parser;
use Twig\Token;
interface InfixExpressionParserInterface extends ExpressionParserInterface
{
/**
* @throws SyntaxError
*/
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression;
public function getAssociativity(): InfixAssociativity;
@@ -11,11 +11,15 @@
namespace Twig\ExpressionParser;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Parser;
use Twig\Token;
interface PrefixExpressionParserInterface extends ExpressionParserInterface
{
/**
* @throws SyntaxError
*/
public function parse(Parser $parser, Token $token): AbstractExpression;
}
+3 -3
View File
@@ -415,9 +415,9 @@ final class CoreExtension extends AbstractExtension
if (!is_countable($values)) {
throw new RuntimeError('The "cycle" function expects a countable sequence as first argument.');
}
$values = self::toArray($values, false);
$values = self::toArray($values, false);
}
}
if (!$count = \count($values)) {
@@ -1817,7 +1817,7 @@ final class CoreExtension extends AbstractExtension
return;
}
throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source);
throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()", "is%1$s()", "has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source);
}
if ($sandboxed) {
+5
View File
@@ -86,6 +86,9 @@ class Parser
return $this->env;
}
/**
* @throws SyntaxError
*/
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
{
$vars = get_object_vars($this);
@@ -170,6 +173,8 @@ class Parser
/**
* @phpstan-impure
*
* @throws SyntaxError
*/
public function subparse($test, bool $dropNeedle = false): Node
{
+1
View File
@@ -22,6 +22,7 @@ namespace Twig\Tests\Extension;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
+2 -2
View File
@@ -85,8 +85,8 @@ class TemplateTest extends TestCase
['{{ 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.(-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.'],
['{{ 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.'],
['{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1.'],
];