diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md index 4c294b66bffc..1a9e94fbc18b 100644 --- a/UPGRADE-7.0.md +++ b/UPGRADE-7.0.md @@ -45,6 +45,11 @@ DoctrineBridge * DoctrineBridge now requires `doctrine/event-manager:^2` * Add parameter `$isSameDatabase` to `DoctrineTokenProvider::configureSchema()` +ExpressionLanguage +------------------ + + * The `in` and `not in` operators now use strict comparison + Filesystem ---------- diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index b06620cd79ac..f54f943ac15d 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.0 +--- + + * The `in` and `not in` operators now use strict comparison + 6.3 --- diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 48331167bd27..d1a98a04fde3 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -30,8 +30,8 @@ class BinaryNode extends Node private const FUNCTIONS = [ '**' => 'pow', '..' => 'range', - 'in' => '\\'.self::class.'::inArray', - 'not in' => '!\\'.self::class.'::inArray', + 'in' => '\\in_array', + 'not in' => '!\\in_array', 'contains' => 'str_contains', 'starts with' => 'str_starts_with', 'ends with' => 'str_ends_with', @@ -71,9 +71,14 @@ public function compile(Compiler $compiler): void ->compile($this->nodes['left']) ->raw(', ') ->compile($this->nodes['right']) - ->raw(')') ; + if ('in' === $operator || 'not in' === $operator) { + $compiler->raw(', true'); + } + + $compiler->raw(')'); + return; } @@ -100,12 +105,11 @@ public function evaluate(array $functions, array $values): mixed if (isset(self::FUNCTIONS[$operator])) { $right = $this->nodes['right']->evaluate($functions, $values); - if ('not in' === $operator) { - return !self::inArray($left, $right); - } - $f = self::FUNCTIONS[$operator]; - - return $f($left, $right); + return match ($operator) { + 'in' => \in_array($left, $right, true), + 'not in' => !\in_array($left, $right, true), + default => self::FUNCTIONS[$operator]($left, $right), + }; } switch ($operator) { @@ -143,9 +147,9 @@ public function evaluate(array $functions, array $values): mixed case '<=': return $left <= $right; case 'not in': - return !self::inArray($left, $right); + return !\in_array($left, $right, true); case 'in': - return self::inArray($left, $right); + return \in_array($left, $right, true); case '+': return $left + $right; case '-': @@ -176,22 +180,6 @@ public function toArray(): array return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; } - /** - * @internal to be replaced by an inline strict call to in_array() in version 7.0 - */ - public static function inArray($value, array $array): bool - { - if (false === $key = array_search($value, $array)) { - return false; - } - - if (!\in_array($value, $array, true)) { - trigger_deprecation('symfony/expression-language', '6.3', 'The "in" operator will use strict comparisons in Symfony 7.0. Loose match found with key "%s" for value %s. Normalize the array parameter so it only has the expected types or implement loose matching in your own expression function.', $key, json_encode($value)); - } - - return true; - } - private function evaluateMatches(string $regexp, ?string $str): int { set_error_handler(function ($t, $m) use ($regexp) { diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index bef2395e859c..b2e072b5785b 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -269,7 +269,7 @@ public function testOperatorCollisions() $expressionLanguage = new ExpressionLanguage(); $expression = 'foo.not in [bar]'; $compiled = $expressionLanguage->compile($expression, ['foo', 'bar']); - $this->assertSame('\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray($foo->not, [0 => $bar])', $compiled); + $this->assertSame('\in_array($foo->not, [0 => $bar], true)', $compiled); $result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']); $this->assertTrue($result); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php index 610c6b0dd289..bfbcd2dd5b75 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\ExpressionLanguage\Tests\Node; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\ExpressionLanguage\Compiler; use Symfony\Component\ExpressionLanguage\Node\ArrayNode; use Symfony\Component\ExpressionLanguage\Node\BinaryNode; @@ -21,8 +20,6 @@ class BinaryNodeTest extends AbstractNodeTestCase { - use ExpectDeprecationTrait; - public static function getEvaluateData(): array { $array = new ArrayNode(); @@ -116,10 +113,10 @@ public static function getCompileData(): array ['pow(5, 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))], ['("a" . "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))], - ['\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("a", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('a'), $array)], - ['\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("c", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('c'), $array)], - ['!\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("c", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)], - ['!\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("a", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)], + ['\in_array("a", [0 => "a", 1 => "b"], true)', new BinaryNode('in', new ConstantNode('a'), $array)], + ['\in_array("c", [0 => "a", 1 => "b"], true)', new BinaryNode('in', new ConstantNode('c'), $array)], + ['!\in_array("c", [0 => "a", 1 => "b"], true)', new BinaryNode('not in', new ConstantNode('c'), $array)], + ['!\in_array("a", [0 => "a", 1 => "b"], true)', new BinaryNode('not in', new ConstantNode('a'), $array)], ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], @@ -219,17 +216,17 @@ public function testCompileMatchesWithInvalidRegexpAsExpression() } /** - * @group legacy + * @testWith [1] + * ["true"] */ - public function testInOperatorStrictness() + public function testInOperatorStrictness(mixed $value) { $array = new ArrayNode(); - $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode('1')); $array->addElement(new ConstantNode(true)); - $node = new BinaryNode('in', new ConstantNode('b'), $array); + $node = new BinaryNode('in', new ConstantNode($value), $array); - $this->expectDeprecation('Since symfony/expression-language 6.3: The "in" operator will use strict comparisons in Symfony 7.0. Loose match found with key "1" for value "b". Normalize the array parameter so it only has the expected types or implement loose matching in your own expression function.'); - $this->assertTrue($node->evaluate([], [])); + $this->assertFalse($node->evaluate([], [])); } } diff --git a/src/Symfony/Component/ExpressionLanguage/composer.json b/src/Symfony/Component/ExpressionLanguage/composer.json index a516235ae9c3..b1652e8c8ee6 100644 --- a/src/Symfony/Component/ExpressionLanguage/composer.json +++ b/src/Symfony/Component/ExpressionLanguage/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/cache": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" },
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: