From e916e9d564ab0e24326f35f53eb62bb131a62edb Mon Sep 17 00:00:00 2001 From: rhertogh Date: Fri, 18 Aug 2023 13:31:09 +0200 Subject: [PATCH] Fix #19914: Fixed `ArrayHelper::keyExists()` and `::remove()` functions when the key is a float and the value is `null` --- framework/CHANGELOG.md | 1 + framework/db/BaseActiveRecord.php | 4 +- framework/helpers/BaseArrayHelper.php | 18 +++++-- tests/framework/helpers/ArrayHelperTest.php | 60 +++++++++++++++++++-- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index eaa8bc61546..4ed527b0a76 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -16,6 +16,7 @@ Yii Framework 2 Change Log - Enh #19884: Added support Enums in Query Builder (sk1t0n) - Bug #19908: Fix associative array cell content rendering in Table widget (rhertogh) - Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh) +- Bug #19914: Fixed `ArrayHelper::keyExists()` and `::remove()` functions when the key is a float and the value is `null` (rhertogh) - Enh #19920: Broadened the accepted type of `Cookie::$expire` from `int` to `int|string|\DateTimeInterface|null` (rhertogh) diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index 2648a48dae4..60f621eb444 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -282,7 +282,7 @@ public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) */ public function __get($name) { - if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { + if (array_key_exists($name, $this->_attributes)) { return $this->_attributes[$name]; } @@ -290,7 +290,7 @@ public function __get($name) return null; } - if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { + if (array_key_exists($name, $this->_related)) { return $this->_related[$name]; } $value = parent::__get($name); diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index 0e615a579db..56411163e1e 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -327,7 +327,12 @@ public static function setValue(&$array, $path, $value) */ public static function remove(&$array, $key, $default = null) { - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + // ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2) + if (is_float($key)) { + $key = (int)$key; + } + + if (is_array($array) && array_key_exists($key, $array)) { $value = $array[$key]; unset($array[$key]); @@ -608,17 +613,20 @@ public static function map($array, $from, $to, $group = null) * Checks if the given array contains the specified key. * This method enhances the `array_key_exists()` function by supporting case-insensitive * key comparison. - * @param string $key the key to check + * @param string|int $key the key to check * @param array|ArrayAccess $array the array with keys to check * @param bool $caseSensitive whether the key comparison should be case-sensitive * @return bool whether the array contains the specified key */ public static function keyExists($key, $array, $caseSensitive = true) { + // ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2) + if (is_float($key)) { + $key = (int)$key; + } + if ($caseSensitive) { - // Function `isset` checks key faster but skips `null`, `array_key_exists` handles this case - // https://www.php.net/manual/en/function.array-key-exists.php#107786 - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + if (is_array($array) && array_key_exists($key, $array)) { return true; } // Cannot use `array_has_key` on Objects for PHP 7.4+, therefore we need to check using [[ArrayAccess::offsetExists()]] diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index 063eeafcaf6..0d706bc552f 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -135,6 +135,29 @@ public function testRemove() $this->assertEquals('defaultValue', $default); } + /** + * @return void + */ + public function testRemoveWithFloat() + { + if (version_compare(PHP_VERSION, '8.1.0', '>=')) { + $this->markTestSkipped('Using floats as array key is deprecated.'); + } + + $array = ['name' => 'b', 'age' => 3, 1.1 => null]; + + $name = ArrayHelper::remove($array, 'name'); + $this->assertEquals($name, 'b'); + $this->assertEquals($array, ['age' => 3, 1.1 => null]); + + $floatVal = ArrayHelper::remove($array, 1.1); + $this->assertNull($floatVal); + $this->assertEquals($array, ['age' => 3]); + + $default = ArrayHelper::remove($array, 'nonExisting', 'defaultValue'); + $this->assertEquals('defaultValue', $default); + } + public function testRemoveValueMultiple() { $array = [ @@ -506,14 +529,21 @@ public function testMergeEmpty() /** * @see https://github.com/yiisoft/yii2/pull/11549 */ - public function test() + public function testGetValueWithFloatKeys() { - $array = []; - $array[1.0] = 'some value'; + if (version_compare(PHP_VERSION, '8.1.0', '>=')) { + $this->markTestSkipped('Using floats as array key is deprecated.'); + } - $result = ArrayHelper::getValue($array, 1.0); + $array = []; + $array[1.1] = 'some value'; + $array[2.1] = null; + $result = ArrayHelper::getValue($array, 1.2); $this->assertEquals('some value', $result); + + $result = ArrayHelper::getValue($array, 2.2); + $this->assertNull($result); } public function testIndex() @@ -712,6 +742,7 @@ public function testKeyExists() 'a' => 1, 'B' => 2, ]; + $this->assertTrue(ArrayHelper::keyExists('a', $array)); $this->assertFalse(ArrayHelper::keyExists('b', $array)); $this->assertTrue(ArrayHelper::keyExists('B', $array)); @@ -723,6 +754,27 @@ public function testKeyExists() $this->assertFalse(ArrayHelper::keyExists('c', $array, false)); } + public function testKeyExistsWithFloat() + { + if (version_compare(PHP_VERSION, '8.1.0', '>=')) { + $this->markTestSkipped('Using floats as array key is deprecated.'); + } + + $array = [ + 1 => 3, + 2.2 => 4, // Note: Floats are cast to ints, which means that the fractional part will be truncated. + 3.3 => null, + ]; + + $this->assertTrue(ArrayHelper::keyExists(1, $array)); + $this->assertTrue(ArrayHelper::keyExists(1.1, $array)); + $this->assertTrue(ArrayHelper::keyExists(2, $array)); + $this->assertTrue(ArrayHelper::keyExists('2', $array)); + $this->assertTrue(ArrayHelper::keyExists(2.2, $array)); + $this->assertTrue(ArrayHelper::keyExists(3, $array)); + $this->assertTrue(ArrayHelper::keyExists(3.3, $array)); + } + public function testKeyExistsArrayAccess() { $array = new TraversableArrayAccessibleObject([