From 3c620dc7d303e9319c42f62c70039934a76349dd Mon Sep 17 00:00:00 2001 From: Adam Kiss Date: Sun, 28 Apr 2024 19:51:43 +0200 Subject: [PATCH] [ExpressionLanguage] Support non-existent names when followed by null coalescing --- .../Component/ExpressionLanguage/CHANGELOG.md | 5 +++ .../Node/NullCoalescedNameNode.php | 45 +++++++++++++++++++ .../Component/ExpressionLanguage/Parser.php | 4 ++ .../Tests/ExpressionLanguageTest.php | 1 + .../Tests/Node/NullCoalescedNameNodeTest.php | 38 ++++++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 src/Symfony/Component/ExpressionLanguage/Node/NullCoalescedNameNode.php create mode 100644 src/Symfony/Component/ExpressionLanguage/Tests/Node/NullCoalescedNameNodeTest.php diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index 1cd4fcb4e8561..4331d722d0457 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Add support for null-coalescing unknown variables + 7.1 --- diff --git a/src/Symfony/Component/ExpressionLanguage/Node/NullCoalescedNameNode.php b/src/Symfony/Component/ExpressionLanguage/Node/NullCoalescedNameNode.php new file mode 100644 index 0000000000000..e4b4f1d41e259 --- /dev/null +++ b/src/Symfony/Component/ExpressionLanguage/Node/NullCoalescedNameNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Adam Kiss + * + * @internal + */ +class NullCoalescedNameNode extends Node +{ + public function __construct(string $name) + { + parent::__construct( + [], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw('$'.$this->attributes['name'].' ?? null'); + } + + public function evaluate(array $functions, array $values): null + { + return null; + } + + public function toArray(): array + { + return [$this->attributes['name'].' ?? null']; + } +} diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php index 1708d18d4e12b..6c64813fa0758 100644 --- a/src/Symfony/Component/ExpressionLanguage/Parser.php +++ b/src/Symfony/Component/ExpressionLanguage/Parser.php @@ -246,6 +246,10 @@ public function parsePrimaryExpression(): Node\Node } else { if (!($this->flags & self::IGNORE_UNKNOWN_VARIABLES)) { if (!\in_array($token->value, $this->names, true)) { + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '??')) { + return new Node\NullCoalescedNameNode($token->value); + } + throw new SyntaxError(sprintf('Variable "%s" is not valid.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names); } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 5fa231885a2a1..fbd50c9117c07 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -433,6 +433,7 @@ public function bar() } }; + yield ['bar ?? "default"', null]; yield ['foo.bar ?? "default"', null]; yield ['foo.bar.baz ?? "default"', (object) ['bar' => null]]; yield ['foo.bar ?? foo.baz ?? "default"', null]; diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/NullCoalescedNameNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/NullCoalescedNameNodeTest.php new file mode 100644 index 0000000000000..c5baef92fe333 --- /dev/null +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/NullCoalescedNameNodeTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\NullCoalescedNameNode; + +class NullCoalescedNameNodeTest extends AbstractNodeTestCase +{ + public static function getEvaluateData(): array + { + return [ + [null, new NullCoalescedNameNode('foo'), []], + ]; + } + + public static function getCompileData(): array + { + return [ + ['$foo ?? null', new NullCoalescedNameNode('foo')], + ]; + } + + public static function getDumpData(): array + { + return [ + ['foo ?? null', new NullCoalescedNameNode('foo')], + ]; + } +}