From 98e7bf7e7445bb5ee44b77f91f01db91db290612 Mon Sep 17 00:00:00 2001 From: lokeshrangani <57695393+lokeshrangani@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:01:30 +0530 Subject: [PATCH 1/4] Add valuePreservingFalsy method to handle falsy values in collections --- .../Collections/Traits/EnumeratesValues.php | 30 +++++ tests/Support/ValuePreservingFalsyTest.php | 118 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 tests/Support/ValuePreservingFalsyTest.php diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index d2894529ed6e..5eeb4a9f4ab0 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -350,6 +350,36 @@ public function value($key, $default = null) return value($default); } + /** + * Get the value of a given key from the first item in the collection, + * even if the value is a falsy one like 0, false, or an empty string. + * + * This method avoids treating falsy values as null or non-existent. + * + * @param string|int $key The key to retrieve from the first matching item. + * @param mixed|null $default Default value if key not found. + * @return mixed + */ + public function valuePreservingFalsy($key, $default = null) + { + $item = $this->first(); + + if ($item === null) { + return value($default); + } + + if (is_array($item) && array_key_exists($key, $item)) { + return $item[$key]; + } + + if (is_object($item) && property_exists($item, $key)) { + return $item->{$key}; + } + + // Fallback to dot notation + return data_get($item, $key, $default); + } + /** * Ensure that every item in the collection is of the expected type. * diff --git a/tests/Support/ValuePreservingFalsyTest.php b/tests/Support/ValuePreservingFalsyTest.php new file mode 100644 index 000000000000..852af53e2b29 --- /dev/null +++ b/tests/Support/ValuePreservingFalsyTest.php @@ -0,0 +1,118 @@ + 'Tim', 'balance' => 0], + ['name' => 'John', 'balance' => 200], + ]); + + $this->assertSame(0, $collection->valuePreservingFalsy('balance')); + } + + // Test when the value is 0.0 (float) + public function test_float_zero_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'balance' => 0.0], + ['name' => 'John', 'balance' => 200.5], + ]); + + $this->assertSame(0.0, $collection->valuePreservingFalsy('balance')); + } + + // Test when the value is false (boolean) + public function test_false_is_preserved() + { + $collection = collect([ + ['name' => 'John', 'vegetarian' => true], + ['name' => 'Tim', 'vegetarian' => false], + ]); + + $this->assertFalse($collection->where('name', 'Tim')->valuePreservingFalsy('vegetarian')); + } + + // Test when the value is an empty string + public function test_empty_string_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'status' => ''], + ['name' => 'John', 'status' => 'active'], + ]); + + $this->assertSame('', $collection->valuePreservingFalsy('status')); + } + + // Test when the value is null + public function test_null_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'age' => null], + ['name' => 'John', 'age' => 30], + ]); + + $this->assertNull($collection->valuePreservingFalsy('age')); + } + + // Test when the value is an empty array + public function test_empty_array_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'tags' => []], + ['name' => 'John', 'tags' => ['admin']], + ]); + + $this->assertSame([], $collection->valuePreservingFalsy('tags')); + } + + // Test when the value is '0' (string zero) + public function test_string_zero_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'balance' => '0'], + ['name' => 'John', 'balance' => '100'], + ]); + + $this->assertSame('0', $collection->valuePreservingFalsy('balance')); + } + + // Test when a missing key is provided + public function test_missing_key_returns_default() + { + $collection = collect([ + ['name' => 'Tim', 'balance' => 0], + ['name' => 'John', 'balance' => 200], + ]); + + $this->assertSame('default_value', $collection->valuePreservingFalsy('missing_key', 'default_value')); + } + + // Test when a falsy value in a subsequent item is returned (e.g. first item is falsy, second is not) + public function test_first_falsy_value_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'status' => 0], + ['name' => 'John', 'status' => 'active'], + ]); + + $this->assertSame(0, $collection->valuePreservingFalsy('status')); + } + + // Test when a falsy value in a subsequent item is returned (string '0' case) + public function test_first_item_with_string_zero_is_preserved() + { + $collection = collect([ + ['name' => 'Tim', 'status' => '0'], + ['name' => 'John', 'status' => 'active'], + ]); + + $this->assertSame('0', $collection->valuePreservingFalsy('status')); + } +} From aad40a5b17deb285d11258b043730f57f3236796 Mon Sep 17 00:00:00 2001 From: lokeshrangani <57695393+lokeshrangani@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:46:44 +0530 Subject: [PATCH 2/4] Add $preserveFalsy flag to value() method to retain falsy values --- .../Collections/Traits/EnumeratesValues.php | 40 ++++--------------- tests/Support/ValuePreservingFalsyTest.php | 20 +++++----- 2 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 5eeb4a9f4ab0..cc4aa27459a6 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -341,43 +341,19 @@ public function firstWhere($key, $operator = null, $value = null) * @param TValueDefault|(\Closure(): TValueDefault) $default * @return TValue|TValueDefault */ - public function value($key, $default = null) + public function value($key, $default = null, $preserveFalsy = false) { - if ($value = $this->firstWhere($key)) { - return data_get($value, $key, $default); + if ($preserveFalsy === true) { + $value = $this->first(); + } else { + $value = $this->firstWhere($key); } - return value($default); - } - - /** - * Get the value of a given key from the first item in the collection, - * even if the value is a falsy one like 0, false, or an empty string. - * - * This method avoids treating falsy values as null or non-existent. - * - * @param string|int $key The key to retrieve from the first matching item. - * @param mixed|null $default Default value if key not found. - * @return mixed - */ - public function valuePreservingFalsy($key, $default = null) - { - $item = $this->first(); - - if ($item === null) { - return value($default); - } - - if (is_array($item) && array_key_exists($key, $item)) { - return $item[$key]; - } - - if (is_object($item) && property_exists($item, $key)) { - return $item->{$key}; + if ($value) { + return data_get($value, $key, $default); } - // Fallback to dot notation - return data_get($item, $key, $default); + return value($default); } /** diff --git a/tests/Support/ValuePreservingFalsyTest.php b/tests/Support/ValuePreservingFalsyTest.php index 852af53e2b29..768d5dc33a9e 100644 --- a/tests/Support/ValuePreservingFalsyTest.php +++ b/tests/Support/ValuePreservingFalsyTest.php @@ -14,7 +14,7 @@ public function test_zero_is_preserved() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame(0, $collection->valuePreservingFalsy('balance')); + $this->assertSame(0, $collection->value('balance', preserveFalsy: true)); } // Test when the value is 0.0 (float) @@ -25,7 +25,7 @@ public function test_float_zero_is_preserved() ['name' => 'John', 'balance' => 200.5], ]); - $this->assertSame(0.0, $collection->valuePreservingFalsy('balance')); + $this->assertSame(0.0, $collection->value('balance', preserveFalsy: true)); } // Test when the value is false (boolean) @@ -36,7 +36,7 @@ public function test_false_is_preserved() ['name' => 'Tim', 'vegetarian' => false], ]); - $this->assertFalse($collection->where('name', 'Tim')->valuePreservingFalsy('vegetarian')); + $this->assertFalse($collection->where('name', 'Tim')->value('vegetarian', preserveFalsy: true)); } // Test when the value is an empty string @@ -47,7 +47,7 @@ public function test_empty_string_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('', $collection->valuePreservingFalsy('status')); + $this->assertSame('', $collection->value('status', preserveFalsy: true)); } // Test when the value is null @@ -58,7 +58,7 @@ public function test_null_is_preserved() ['name' => 'John', 'age' => 30], ]); - $this->assertNull($collection->valuePreservingFalsy('age')); + $this->assertNull($collection->value('age', preserveFalsy: true)); } // Test when the value is an empty array @@ -69,7 +69,7 @@ public function test_empty_array_is_preserved() ['name' => 'John', 'tags' => ['admin']], ]); - $this->assertSame([], $collection->valuePreservingFalsy('tags')); + $this->assertSame([], $collection->value('tags', preserveFalsy: true)); } // Test when the value is '0' (string zero) @@ -80,7 +80,7 @@ public function test_string_zero_is_preserved() ['name' => 'John', 'balance' => '100'], ]); - $this->assertSame('0', $collection->valuePreservingFalsy('balance')); + $this->assertSame('0', $collection->value('balance', preserveFalsy: true)); } // Test when a missing key is provided @@ -91,7 +91,7 @@ public function test_missing_key_returns_default() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame('default_value', $collection->valuePreservingFalsy('missing_key', 'default_value')); + $this->assertSame('default_value', $collection->value('missing_key', 'default_value', preserveFalsy: true)); } // Test when a falsy value in a subsequent item is returned (e.g. first item is falsy, second is not) @@ -102,7 +102,7 @@ public function test_first_falsy_value_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame(0, $collection->valuePreservingFalsy('status')); + $this->assertSame(0, $collection->value('status', preserveFalsy: true)); } // Test when a falsy value in a subsequent item is returned (string '0' case) @@ -113,6 +113,6 @@ public function test_first_item_with_string_zero_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('0', $collection->valuePreservingFalsy('status')); + $this->assertSame('0', $collection->value('status', preserveFalsy: true)); } } From e90fbf2af11ef73d8e80e8d69aeb4a63d35c7fac Mon Sep 17 00:00:00 2001 From: lokeshrangani <57695393+lokeshrangani@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:00:28 +0530 Subject: [PATCH 3/4] Add valuePreservingFalsy method to handle falsy values in collections --- .../Collections/Traits/EnumeratesValues.php | 25 ++++++++++++++----- tests/Support/ValuePreservingFalsyTest.php | 20 +++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index cc4aa27459a6..4cbe1fadf97a 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -341,15 +341,28 @@ public function firstWhere($key, $operator = null, $value = null) * @param TValueDefault|(\Closure(): TValueDefault) $default * @return TValue|TValueDefault */ - public function value($key, $default = null, $preserveFalsy = false) + public function value($key, $default = null) { - if ($preserveFalsy === true) { - $value = $this->first(); - } else { - $value = $this->firstWhere($key); + if ($value = $this->firstWhere($key)) { + return data_get($value, $key, $default); } - if ($value) { + return value($default); + } + + /** + * Get the value of a given key from the first item in the collection, + * even if the value is a falsy one like 0, false, or an empty string. + * + * This method avoids treating falsy values as null or non-existent. + * + * @param string|int $key The key to retrieve from the first matching item. + * @param mixed|null $default Default value if key not found. + * @return mixed + */ + public function valuePreservingFalsy($key, $default = null) + { + if ($value = $this->first()) { return data_get($value, $key, $default); } diff --git a/tests/Support/ValuePreservingFalsyTest.php b/tests/Support/ValuePreservingFalsyTest.php index 768d5dc33a9e..852af53e2b29 100644 --- a/tests/Support/ValuePreservingFalsyTest.php +++ b/tests/Support/ValuePreservingFalsyTest.php @@ -14,7 +14,7 @@ public function test_zero_is_preserved() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame(0, $collection->value('balance', preserveFalsy: true)); + $this->assertSame(0, $collection->valuePreservingFalsy('balance')); } // Test when the value is 0.0 (float) @@ -25,7 +25,7 @@ public function test_float_zero_is_preserved() ['name' => 'John', 'balance' => 200.5], ]); - $this->assertSame(0.0, $collection->value('balance', preserveFalsy: true)); + $this->assertSame(0.0, $collection->valuePreservingFalsy('balance')); } // Test when the value is false (boolean) @@ -36,7 +36,7 @@ public function test_false_is_preserved() ['name' => 'Tim', 'vegetarian' => false], ]); - $this->assertFalse($collection->where('name', 'Tim')->value('vegetarian', preserveFalsy: true)); + $this->assertFalse($collection->where('name', 'Tim')->valuePreservingFalsy('vegetarian')); } // Test when the value is an empty string @@ -47,7 +47,7 @@ public function test_empty_string_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('', $collection->value('status', preserveFalsy: true)); + $this->assertSame('', $collection->valuePreservingFalsy('status')); } // Test when the value is null @@ -58,7 +58,7 @@ public function test_null_is_preserved() ['name' => 'John', 'age' => 30], ]); - $this->assertNull($collection->value('age', preserveFalsy: true)); + $this->assertNull($collection->valuePreservingFalsy('age')); } // Test when the value is an empty array @@ -69,7 +69,7 @@ public function test_empty_array_is_preserved() ['name' => 'John', 'tags' => ['admin']], ]); - $this->assertSame([], $collection->value('tags', preserveFalsy: true)); + $this->assertSame([], $collection->valuePreservingFalsy('tags')); } // Test when the value is '0' (string zero) @@ -80,7 +80,7 @@ public function test_string_zero_is_preserved() ['name' => 'John', 'balance' => '100'], ]); - $this->assertSame('0', $collection->value('balance', preserveFalsy: true)); + $this->assertSame('0', $collection->valuePreservingFalsy('balance')); } // Test when a missing key is provided @@ -91,7 +91,7 @@ public function test_missing_key_returns_default() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame('default_value', $collection->value('missing_key', 'default_value', preserveFalsy: true)); + $this->assertSame('default_value', $collection->valuePreservingFalsy('missing_key', 'default_value')); } // Test when a falsy value in a subsequent item is returned (e.g. first item is falsy, second is not) @@ -102,7 +102,7 @@ public function test_first_falsy_value_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame(0, $collection->value('status', preserveFalsy: true)); + $this->assertSame(0, $collection->valuePreservingFalsy('status')); } // Test when a falsy value in a subsequent item is returned (string '0' case) @@ -113,6 +113,6 @@ public function test_first_item_with_string_zero_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('0', $collection->value('status', preserveFalsy: true)); + $this->assertSame('0', $collection->valuePreservingFalsy('status')); } } From 93b2f750b9e2f6d5259dce7ccac074bf28753a5b Mon Sep 17 00:00:00 2001 From: lokeshrangani <57695393+lokeshrangani@users.noreply.github.com> Date: Mon, 28 Apr 2025 20:26:46 +0530 Subject: [PATCH 4/4] Enhance value() method to support preserving falsy values --- .../Collections/Traits/EnumeratesValues.php | 24 ++++--------------- tests/Support/ValuePreservingFalsyTest.php | 20 ++++++++-------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 4cbe1fadf97a..53c7ff0f38cb 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -339,30 +339,14 @@ public function firstWhere($key, $operator = null, $value = null) * * @param string $key * @param TValueDefault|(\Closure(): TValueDefault) $default + * @param bool $allowFalsy * @return TValue|TValueDefault */ - public function value($key, $default = null) + public function value($key, $default = null, $allowFalsy = false) { - if ($value = $this->firstWhere($key)) { - return data_get($value, $key, $default); - } - - return value($default); - } + $value = $allowFalsy ? $this->first() : $this->firstWhere($key); - /** - * Get the value of a given key from the first item in the collection, - * even if the value is a falsy one like 0, false, or an empty string. - * - * This method avoids treating falsy values as null or non-existent. - * - * @param string|int $key The key to retrieve from the first matching item. - * @param mixed|null $default Default value if key not found. - * @return mixed - */ - public function valuePreservingFalsy($key, $default = null) - { - if ($value = $this->first()) { + if ($value) { return data_get($value, $key, $default); } diff --git a/tests/Support/ValuePreservingFalsyTest.php b/tests/Support/ValuePreservingFalsyTest.php index 852af53e2b29..53c9e06d29ff 100644 --- a/tests/Support/ValuePreservingFalsyTest.php +++ b/tests/Support/ValuePreservingFalsyTest.php @@ -14,7 +14,7 @@ public function test_zero_is_preserved() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame(0, $collection->valuePreservingFalsy('balance')); + $this->assertSame(0, $collection->value('balance', allowFalsy: true)); } // Test when the value is 0.0 (float) @@ -25,7 +25,7 @@ public function test_float_zero_is_preserved() ['name' => 'John', 'balance' => 200.5], ]); - $this->assertSame(0.0, $collection->valuePreservingFalsy('balance')); + $this->assertSame(0.0, $collection->value('balance', allowFalsy: true)); } // Test when the value is false (boolean) @@ -36,7 +36,7 @@ public function test_false_is_preserved() ['name' => 'Tim', 'vegetarian' => false], ]); - $this->assertFalse($collection->where('name', 'Tim')->valuePreservingFalsy('vegetarian')); + $this->assertFalse($collection->where('name', 'Tim')->value('vegetarian', allowFalsy: true)); } // Test when the value is an empty string @@ -47,7 +47,7 @@ public function test_empty_string_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('', $collection->valuePreservingFalsy('status')); + $this->assertSame('', $collection->value('status', allowFalsy: true)); } // Test when the value is null @@ -58,7 +58,7 @@ public function test_null_is_preserved() ['name' => 'John', 'age' => 30], ]); - $this->assertNull($collection->valuePreservingFalsy('age')); + $this->assertNull($collection->value('age', allowFalsy: true)); } // Test when the value is an empty array @@ -69,7 +69,7 @@ public function test_empty_array_is_preserved() ['name' => 'John', 'tags' => ['admin']], ]); - $this->assertSame([], $collection->valuePreservingFalsy('tags')); + $this->assertSame([], $collection->value('tags', allowFalsy: true)); } // Test when the value is '0' (string zero) @@ -80,7 +80,7 @@ public function test_string_zero_is_preserved() ['name' => 'John', 'balance' => '100'], ]); - $this->assertSame('0', $collection->valuePreservingFalsy('balance')); + $this->assertSame('0', $collection->value('balance', allowFalsy: true)); } // Test when a missing key is provided @@ -91,7 +91,7 @@ public function test_missing_key_returns_default() ['name' => 'John', 'balance' => 200], ]); - $this->assertSame('default_value', $collection->valuePreservingFalsy('missing_key', 'default_value')); + $this->assertSame('default_value', $collection->value('missing_key', 'default_value', allowFalsy: true)); } // Test when a falsy value in a subsequent item is returned (e.g. first item is falsy, second is not) @@ -102,7 +102,7 @@ public function test_first_falsy_value_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame(0, $collection->valuePreservingFalsy('status')); + $this->assertSame(0, $collection->value('status', allowFalsy: true)); } // Test when a falsy value in a subsequent item is returned (string '0' case) @@ -113,6 +113,6 @@ public function test_first_item_with_string_zero_is_preserved() ['name' => 'John', 'status' => 'active'], ]); - $this->assertSame('0', $collection->valuePreservingFalsy('status')); + $this->assertSame('0', $collection->value('status', allowFalsy: true)); } }