diff --git a/CHANGELOG b/CHANGELOG index c930ffc2be0..3c46897fdb5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.28.1 (2026-XX-XX) + * Fix array access with a `Stringable` key coercing the key to string for `ArrayAccess` objects that use object keys (such as `SplObjectStorage`) * Fix duplicated macro argument names triggering a PHP fatal error instead of a `SyntaxError` # 3.28.0 (2026-07-03) diff --git a/src/Node/Expression/GetAttrExpression.php b/src/Node/Expression/GetAttrExpression.php index 3dcd88af526..71e97bf48f4 100644 --- a/src/Node/Expression/GetAttrExpression.php +++ b/src/Node/Expression/GetAttrExpression.php @@ -74,7 +74,7 @@ public function compile(Compiler $compiler): void ->raw($var) ->raw('[') ; - $this->compileArrayKey($compiler); + $this->compileArrayKey($compiler, $var); $compiler->raw('] ?? null) : null)'); return; @@ -91,7 +91,7 @@ public function compile(Compiler $compiler): void ->raw($var) ->raw('[') ; - $this->compileArrayKey($compiler); + $this->compileArrayKey($compiler, $var); $compiler->raw('] ?? null) : '); } @@ -177,9 +177,11 @@ public function getStringCoercedChildNames(): array /** * Coerces a Stringable array key to string so the optimized path matches - * CoreExtension::getAttribute(); scalars are left to PHP's native offset coercion. + * CoreExtension::getAttribute(): only arrays coerce the key, while ArrayAccess + * objects (e.g. SplObjectStorage) receive it untouched. Scalars are left to + * PHP's native offset coercion. */ - private function compileArrayKey(Compiler $compiler): void + private function compileArrayKey(Compiler $compiler, string $var): void { $attribute = $this->getNode('attribute'); @@ -193,7 +195,7 @@ private function compileArrayKey(Compiler $compiler): void $compiler ->raw('(('.$key.' = ') ->subcompile($attribute) - ->raw(') instanceof \Stringable ? (string) '.$key.' : '.$key.')') + ->raw(') instanceof \Stringable && is_array('.$var.') ? (string) '.$key.' : '.$key.')') ; } diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index 05f56f6e1bb..79580ff405f 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -284,6 +284,29 @@ public static function getStrictVariablesModes(): iterable yield 'strict' => [true]; } + /** + * @dataProvider getStrictVariablesModes + */ + #[DataProvider('getStrictVariablesModes')] + public function testArrayAccessWithObjectKeyKeepsTheObjectKey(bool $strict) + { + $twig = new Environment(new ArrayLoader(['index' => '{{ data[object] }}']), [ + 'strict_variables' => $strict, + 'autoescape' => false, + ]); + + $object = new class implements \Stringable { + public function __toString(): string + { + return 'string'; + } + }; + $data = new \SplObjectStorage(); + $data[$object] = 'value'; + + $this->assertSame('value', $twig->render('index', ['data' => $data, 'object' => $object])); + } + public function testArrayAccessWithStringableKeyIsCheckedBySandbox() { $object = new class implements \Stringable {