From ee40edac432f00414fc2dc4ec1481c9b1fc2777a Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 7 May 2025 13:23:27 +0200 Subject: [PATCH 1/9] [12.x] Introduce Arr::from() --- src/Illuminate/Collections/Arr.php | 30 +++++++++ .../Collections/Traits/EnumeratesValues.php | 16 +---- tests/Support/Common.php | 62 +++++++++++++++++++ tests/Support/SupportArrTest.php | 20 ++++++ tests/Support/SupportCollectionTest.php | 60 +----------------- 5 files changed, 115 insertions(+), 73 deletions(-) create mode 100644 tests/Support/Common.php diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index d9b7561db2cf..6e27245dacd7 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -4,9 +4,15 @@ use ArgumentCountError; use ArrayAccess; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; +use JsonSerializable; use Random\Randomizer; +use Traversable; +use UnitEnum; +use WeakMap; class Arr { @@ -378,6 +384,30 @@ public static function forget(&$array, $keys) } } + /** + * Get the underlying array of items from the given argument. + * + * @template TKey of array-key = array-key + * @template TValue = mixed + * + * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|UnitEnum $items + * @return ($items is WeakMap ? list : array) + */ + public static function from($items) + { + return match (true) { + is_array($items) => $items, + $items instanceof Enumerable => $items->all(), + $items instanceof Arrayable => $items->toArray(), + $items instanceof WeakMap => iterator_to_array($items, false), + $items instanceof Traversable => iterator_to_array($items), + $items instanceof Jsonable => json_decode($items->toJson(), true), + $items instanceof JsonSerializable => (array) $items->jsonSerialize(), + $items instanceof UnitEnum => [$items], + default => (array) $items, + }; + } + /** * Get an item from an array using "dot" notation. * diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index d2894529ed6e..9cf22344a891 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -12,12 +12,8 @@ use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; -use InvalidArgumentException; use JsonSerializable; -use Traversable; use UnexpectedValueException; -use UnitEnum; -use WeakMap; use function Illuminate\Support\enum_value; @@ -1059,17 +1055,7 @@ public function __get($key) */ protected function getArrayableItems($items) { - return match (true) { - is_array($items) => $items, - $items instanceof WeakMap => throw new InvalidArgumentException('Collections can not be created using instances of WeakMap.'), - $items instanceof Enumerable => $items->all(), - $items instanceof Arrayable => $items->toArray(), - $items instanceof Traversable => iterator_to_array($items), - $items instanceof Jsonable => json_decode($items->toJson(), true), - $items instanceof JsonSerializable => (array) $items->jsonSerialize(), - $items instanceof UnitEnum => [$items], - default => (array) $items, - }; + return Arr::from($items); } /** diff --git a/tests/Support/Common.php b/tests/Support/Common.php new file mode 100644 index 000000000000..2a5c5b333a21 --- /dev/null +++ b/tests/Support/Common.php @@ -0,0 +1,62 @@ + 'bar']; + } +} + +class TestJsonableObject implements Jsonable +{ + public function toJson($options = 0) + { + return '{"foo":"bar"}'; + } +} + +class TestJsonSerializeObject implements JsonSerializable +{ + public function jsonSerialize(): array + { + return ['foo' => 'bar']; + } +} + +class TestJsonSerializeWithScalarValueObject implements JsonSerializable +{ + public function jsonSerialize(): string + { + return 'foo'; + } +} + +class TestTraversableAndJsonSerializableObject implements IteratorAggregate, JsonSerializable +{ + public $items; + + public function __construct($items) + { + $this->items = $items; + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + public function jsonSerialize(): array + { + return json_decode(json_encode($this->items), true); + } +} diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index e5d15a06362f..18a7a4b10837 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -11,6 +11,9 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use stdClass; +use WeakMap; + +include_once 'Common.php'; class SupportArrTest extends TestCase { @@ -1485,6 +1488,23 @@ public function testForget() $this->assertEquals([2 => [1 => 'products']], $array); } + public function testFrom() + { + $this->assertSame(['foo' => 'bar'], Arr::from(['foo' => 'bar'])); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestArrayableObject)); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonableObject)); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonSerializeObject)); + $this->assertSame(['foo'], Arr::from(new TestJsonSerializeWithScalarValueObject)); + + $subject = [new stdClass, new stdClass]; + $items = new TestTraversableAndJsonSerializableObject($subject); + $this->assertSame($subject, Arr::from($items)); + + $items = new WeakMap; + $items[$temp = new class {}] = 'bar'; + $this->assertSame(['bar'], Arr::from($items)); + } + public function testWrap() { $string = 'a'; diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 7c8104b570ae..513e1fa4187a 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -8,7 +8,6 @@ use CachingIterator; use Exception; use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Contracts\Support\Jsonable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Illuminate\Support\HtmlString; @@ -18,7 +17,6 @@ use Illuminate\Support\Str; use Illuminate\Support\Stringable; use InvalidArgumentException; -use IteratorAggregate; use JsonSerializable; use Mockery as m; use PHPUnit\Framework\Attributes\DataProvider; @@ -26,10 +24,10 @@ use ReflectionClass; use stdClass; use Symfony\Component\VarDumper\VarDumper; -use Traversable; use UnexpectedValueException; use WeakMap; +include_once 'Common.php'; include_once 'Enums.php'; class SupportCollectionTest extends TestCase @@ -2915,14 +2913,12 @@ public function testConstructMethodFromObject($collection) #[DataProvider('collectionClassProvider')] public function testConstructMethodFromWeakMap($collection) { - $this->expectException('InvalidArgumentException'); - $map = new WeakMap(); $object = new stdClass; $object->foo = 'bar'; $map[$object] = 3; - $data = new $collection($map); + $this->assertEquals([3], $data->all()); } public function testSplice() @@ -5787,30 +5783,6 @@ public function offsetUnset($offset): void } } -class TestArrayableObject implements Arrayable -{ - public function toArray() - { - return ['foo' => 'bar']; - } -} - -class TestJsonableObject implements Jsonable -{ - public function toJson($options = 0) - { - return '{"foo":"bar"}'; - } -} - -class TestJsonSerializeObject implements JsonSerializable -{ - public function jsonSerialize(): array - { - return ['foo' => 'bar']; - } -} - class TestJsonSerializeToStringObject implements JsonSerializable { public function jsonSerialize(): string @@ -5819,34 +5791,6 @@ public function jsonSerialize(): string } } -class TestJsonSerializeWithScalarValueObject implements JsonSerializable -{ - public function jsonSerialize(): string - { - return 'foo'; - } -} - -class TestTraversableAndJsonSerializableObject implements IteratorAggregate, JsonSerializable -{ - public $items; - - public function __construct($items) - { - $this->items = $items; - } - - public function getIterator(): Traversable - { - return new ArrayIterator($this->items); - } - - public function jsonSerialize(): array - { - return json_decode(json_encode($this->items), true); - } -} - class TestCollectionMapIntoObject { public $value; From 9e7c7d72ad83020d4c09a51a5866695930d72bd3 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 7 May 2025 15:55:22 +0200 Subject: [PATCH 2/9] Arr::from should not wrap scalar values --- src/Illuminate/Collections/Arr.php | 4 +++- src/Illuminate/Collections/Traits/EnumeratesValues.php | 7 ++++++- tests/Support/SupportArrTest.php | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 6e27245dacd7..bd0d74b6742c 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -392,6 +392,8 @@ public static function forget(&$array, $keys) * * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|UnitEnum $items * @return ($items is WeakMap ? list : array) + * + * @throws \InvalidArgumentException */ public static function from($items) { @@ -404,7 +406,7 @@ public static function from($items) $items instanceof Jsonable => json_decode($items->toJson(), true), $items instanceof JsonSerializable => (array) $items->jsonSerialize(), $items instanceof UnitEnum => [$items], - default => (array) $items, + default => throw new InvalidArgumentException('Items cannot be represented by a scalar value.'), }; } diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 9cf22344a891..adc95a2a5473 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -12,6 +12,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; +use InvalidArgumentException; use JsonSerializable; use UnexpectedValueException; @@ -1055,7 +1056,11 @@ public function __get($key) */ protected function getArrayableItems($items) { - return Arr::from($items); + try { + return Arr::from($items); + } catch (InvalidArgumentException) { + return (array) $items; + } } /** diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 18a7a4b10837..237c945dd96d 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -1503,6 +1503,10 @@ public function testFrom() $items = new WeakMap; $items[$temp = new class {}] = 'bar'; $this->assertSame(['bar'], Arr::from($items)); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); + Arr::from(123); } public function testWrap() From ddf8a5c2e9912831006555384c94c87f2daef955 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 7 May 2025 17:04:41 +0200 Subject: [PATCH 3/9] Arr::arrayable helper function --- src/Illuminate/Collections/Arr.php | 16 ++++++++++++++++ tests/Support/Common.php | 2 +- tests/Support/SupportArrTest.php | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index bd0d74b6742c..c904c31a0085 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -29,6 +29,22 @@ public static function accessible($value) return is_array($value) || $value instanceof ArrayAccess; } + /** + * Determine whether the given value is arrayable. + * + * @param mixed $value + * @return bool + */ + public static function arrayable($value) + { + return is_array($value) + || $value instanceof Arrayable + || $value instanceof Traversable + || $value instanceof Jsonable + || $value instanceof JsonSerializable + || $value instanceof UnitEnum; + } + /** * Add an element to an array using "dot" notation if it doesn't exist. * diff --git a/tests/Support/Common.php b/tests/Support/Common.php index 2a5c5b333a21..7928b8705624 100644 --- a/tests/Support/Common.php +++ b/tests/Support/Common.php @@ -45,7 +45,7 @@ class TestTraversableAndJsonSerializableObject implements IteratorAggregate, Jso { public $items; - public function __construct($items) + public function __construct($items = []) { $this->items = $items; } diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 237c945dd96d..c408c45e844b 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -35,6 +35,25 @@ public function testAccessible(): void $this->assertFalse(Arr::accessible(static fn () => null)); } + public function testArrayable(): void + { + $this->assertTrue(Arr::arrayable([])); + $this->assertTrue(Arr::arrayable(new TestArrayableObject)); + $this->assertTrue(Arr::arrayable(new TestJsonableObject)); + $this->assertTrue(Arr::arrayable(new TestJsonSerializeObject)); + $this->assertTrue(Arr::arrayable(new TestTraversableAndJsonSerializableObject)); + + $this->assertFalse(Arr::arrayable(null)); + $this->assertFalse(Arr::arrayable('abc')); + $this->assertFalse(Arr::arrayable(new stdClass)); + $this->assertFalse(Arr::arrayable((object) ['a' => 1, 'b' => 2])); + $this->assertFalse(Arr::arrayable(123)); + $this->assertFalse(Arr::arrayable(12.34)); + $this->assertFalse(Arr::arrayable(true)); + $this->assertFalse(Arr::arrayable(new \DateTime)); + $this->assertFalse(Arr::arrayable(static fn () => null)); + } + public function testAdd() { $array = Arr::add(['name' => 'Desk'], 'price', 100); From 6dd3dd8ce83cea00b1ea43f98702e7f2125ed9b5 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 7 May 2025 19:54:35 +0200 Subject: [PATCH 4/9] Arr::from can be used with raw objects except enums; handle UnitEnum instances as scalars --- src/Illuminate/Collections/Arr.php | 7 +++---- src/Illuminate/Collections/Traits/EnumeratesValues.php | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index c904c31a0085..9777a1acf8f4 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -41,8 +41,7 @@ public static function arrayable($value) || $value instanceof Arrayable || $value instanceof Traversable || $value instanceof Jsonable - || $value instanceof JsonSerializable - || $value instanceof UnitEnum; + || $value instanceof JsonSerializable; } /** @@ -406,7 +405,7 @@ public static function forget(&$array, $keys) * @template TKey of array-key = array-key * @template TValue = mixed * - * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|UnitEnum $items + * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|object $items * @return ($items is WeakMap ? list : array) * * @throws \InvalidArgumentException @@ -421,7 +420,7 @@ public static function from($items) $items instanceof Traversable => iterator_to_array($items), $items instanceof Jsonable => json_decode($items->toJson(), true), $items instanceof JsonSerializable => (array) $items->jsonSerialize(), - $items instanceof UnitEnum => [$items], + is_object($items) && ! $items instanceof UnitEnum => (array) $items, default => throw new InvalidArgumentException('Items cannot be represented by a scalar value.'), }; } diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index adc95a2a5473..8bb91d5716d3 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -1059,7 +1059,7 @@ protected function getArrayableItems($items) try { return Arr::from($items); } catch (InvalidArgumentException) { - return (array) $items; + return Arr::wrap($items); } } From 7d8fe6ba7ff807b993c485c86e8cc5d2c393ebd9 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 11:09:37 +0200 Subject: [PATCH 5/9] Add tests --- tests/Support/SupportArrTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index c408c45e844b..7b1e7380ed10 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -14,6 +14,7 @@ use WeakMap; include_once 'Common.php'; +include_once 'Enums.php'; class SupportArrTest extends TestCase { @@ -1510,6 +1511,7 @@ public function testForget() public function testFrom() { $this->assertSame(['foo' => 'bar'], Arr::from(['foo' => 'bar'])); + $this->assertSame(['foo' => 'bar'], Arr::from((object) ['foo' => 'bar'])); $this->assertSame(['foo' => 'bar'], Arr::from(new TestArrayableObject)); $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonableObject)); $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonSerializeObject)); @@ -1526,6 +1528,10 @@ public function testFrom() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); Arr::from(123); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); + Arr::from(TestEnum::A); } public function testWrap() From 2405861452322e09fd1bd6dccc4299ef774ba150 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 12:35:40 +0200 Subject: [PATCH 6/9] Replace implicit `getArrayableItems` usage with `Arr::from` --- src/Illuminate/Collections/helpers.php | 4 ++-- .../Foundation/Testing/DatabaseTruncation.php | 3 ++- src/Illuminate/Support/Str.php | 21 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 55844559e711..16c8f0118993 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -77,9 +77,9 @@ function data_get($target, $key, $default = null) $segment = match ($segment) { '\*' => '*', '\{first}' => '{first}', - '{first}' => array_key_first(is_array($target) ? $target : (new Collection($target))->all()), + '{first}' => array_key_first(Arr::from($target)), '\{last}' => '{last}', - '{last}' => array_key_last(is_array($target) ? $target : (new Collection($target))->all()), + '{last}' => array_key_last(Arr::from($target)), default => $segment, }; diff --git a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index 9ed063241a8f..a84c082343b7 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\ConnectionInterface; use Illuminate\Foundation\Testing\Traits\CanConfigureMigrationCommands; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; trait DatabaseTruncation @@ -120,7 +121,7 @@ protected function getAllTablesForConnection(ConnectionInterface $connection, ?s $schema = $connection->getSchemaBuilder(); - return static::$allTables[$name] = (new Collection($schema->getTables($schema->getCurrentSchemaListing())))->all(); + return static::$allTables[$name] = Arr::from($schema->getTables($schema->getCurrentSchemaListing())); } /** diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 3bb47011d654..123a37a85b27 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -18,7 +18,6 @@ use Ramsey\Uuid\UuidFactory; use Symfony\Component\Uid\Ulid; use Throwable; -use Traversable; use voku\helper\ASCII; class Str @@ -1179,8 +1178,8 @@ public static function repeat(string $string, int $times) */ public static function replaceArray($search, $replace, $subject) { - if ($replace instanceof Traversable) { - $replace = (new Collection($replace))->all(); + if (Arr::arrayable($replace)) { + $replace = Arr::from($replace); } $segments = explode($search, $subject); @@ -1221,16 +1220,16 @@ private static function toStringOr($value, $fallback) */ public static function replace($search, $replace, $subject, $caseSensitive = true) { - if ($search instanceof Traversable) { - $search = (new Collection($search))->all(); + if (Arr::arrayable($search)) { + $search = Arr::from($search); } - if ($replace instanceof Traversable) { - $replace = (new Collection($replace))->all(); + if (Arr::arrayable($replace)) { + $replace = Arr::from($replace); } - if ($subject instanceof Traversable) { - $subject = (new Collection($subject))->all(); + if (Arr::arrayable($subject)) { + $subject = Arr::from($subject); } return $caseSensitive @@ -1362,8 +1361,8 @@ public static function replaceMatches($pattern, $replace, $subject, $limit = -1) */ public static function remove($search, $subject, $caseSensitive = true) { - if ($search instanceof Traversable) { - $search = (new Collection($search))->all(); + if (Arr::arrayable($search)) { + $search = Arr::from($search); } return $caseSensitive From 4fdbb89c87d42708fcc1c8c4697c038eecfa0ad6 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 13:01:23 +0200 Subject: [PATCH 7/9] Handle enums as regular objects in `Arr::from`; wrap enums in `getArrayableItems` --- src/Illuminate/Collections/Arr.php | 3 +-- src/Illuminate/Collections/Traits/EnumeratesValues.php | 10 ++++------ tests/Support/SupportArrTest.php | 8 ++++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 9777a1acf8f4..bea43ce76c26 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -11,7 +11,6 @@ use JsonSerializable; use Random\Randomizer; use Traversable; -use UnitEnum; use WeakMap; class Arr @@ -420,7 +419,7 @@ public static function from($items) $items instanceof Traversable => iterator_to_array($items), $items instanceof Jsonable => json_decode($items->toJson(), true), $items instanceof JsonSerializable => (array) $items->jsonSerialize(), - is_object($items) && ! $items instanceof UnitEnum => (array) $items, + is_object($items) => (array) $items, default => throw new InvalidArgumentException('Items cannot be represented by a scalar value.'), }; } diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 8bb91d5716d3..c11c9c434d89 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -12,9 +12,9 @@ use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; -use InvalidArgumentException; use JsonSerializable; use UnexpectedValueException; +use UnitEnum; use function Illuminate\Support\enum_value; @@ -1056,11 +1056,9 @@ public function __get($key) */ protected function getArrayableItems($items) { - try { - return Arr::from($items); - } catch (InvalidArgumentException) { - return Arr::wrap($items); - } + return is_null($items) || is_scalar($items) || $items instanceof UnitEnum + ? Arr::wrap($items) + : Arr::from($items); } /** diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 7b1e7380ed10..a81e6eb79bee 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -1517,6 +1517,10 @@ public function testFrom() $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonSerializeObject)); $this->assertSame(['foo'], Arr::from(new TestJsonSerializeWithScalarValueObject)); + $this->assertSame(['name' => 'A'], Arr::from(TestEnum::A)); + $this->assertSame(['name' => 'A', 'value' => 1], Arr::from(TestBackedEnum::A)); + $this->assertSame(['name' => 'A', 'value' => 'A'], Arr::from(TestStringBackedEnum::A)); + $subject = [new stdClass, new stdClass]; $items = new TestTraversableAndJsonSerializableObject($subject); $this->assertSame($subject, Arr::from($items)); @@ -1528,10 +1532,6 @@ public function testFrom() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); Arr::from(123); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); - Arr::from(TestEnum::A); } public function testWrap() From a2e6bf00cbfbcc358e41ef91ebd97723ea588029 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 15:34:55 +0200 Subject: [PATCH 8/9] Remove useless `Arr::arrayable` checks --- src/Illuminate/Support/Str.php | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 123a37a85b27..7ce6e867726a 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1178,9 +1178,7 @@ public static function repeat(string $string, int $times) */ public static function replaceArray($search, $replace, $subject) { - if (Arr::arrayable($replace)) { - $replace = Arr::from($replace); - } + $replace = Arr::from($replace); $segments = explode($search, $subject); @@ -1220,17 +1218,9 @@ private static function toStringOr($value, $fallback) */ public static function replace($search, $replace, $subject, $caseSensitive = true) { - if (Arr::arrayable($search)) { - $search = Arr::from($search); - } - - if (Arr::arrayable($replace)) { - $replace = Arr::from($replace); - } - - if (Arr::arrayable($subject)) { - $subject = Arr::from($subject); - } + $search = Arr::from($search); + $replace = Arr::from($replace); + $subject = Arr::from($subject); return $caseSensitive ? str_replace($search, $replace, $subject) @@ -1361,9 +1351,7 @@ public static function replaceMatches($pattern, $replace, $subject, $limit = -1) */ public static function remove($search, $subject, $caseSensitive = true) { - if (Arr::arrayable($search)) { - $search = Arr::from($search); - } + $search = Arr::from($search); return $caseSensitive ? str_replace($search, '', $subject) From 61bef4d95c25f7ae11b2b9ce9b1169893402d8c8 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 15:43:34 +0200 Subject: [PATCH 9/9] Revert to `instanceof Traversable` checks --- src/Illuminate/Support/Str.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 7ce6e867726a..27bdb6bf0189 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -18,6 +18,7 @@ use Ramsey\Uuid\UuidFactory; use Symfony\Component\Uid\Ulid; use Throwable; +use Traversable; use voku\helper\ASCII; class Str @@ -1178,7 +1179,9 @@ public static function repeat(string $string, int $times) */ public static function replaceArray($search, $replace, $subject) { - $replace = Arr::from($replace); + if ($replace instanceof Traversable) { + $replace = Arr::from($replace); + } $segments = explode($search, $subject); @@ -1218,9 +1221,17 @@ private static function toStringOr($value, $fallback) */ public static function replace($search, $replace, $subject, $caseSensitive = true) { - $search = Arr::from($search); - $replace = Arr::from($replace); - $subject = Arr::from($subject); + if ($search instanceof Traversable) { + $search = Arr::from($search); + } + + if ($replace instanceof Traversable) { + $replace = Arr::from($replace); + } + + if ($subject instanceof Traversable) { + $subject = Arr::from($subject); + } return $caseSensitive ? str_replace($search, $replace, $subject) @@ -1351,7 +1362,9 @@ public static function replaceMatches($pattern, $replace, $subject, $limit = -1) */ public static function remove($search, $subject, $caseSensitive = true) { - $search = Arr::from($search); + if ($search instanceof Traversable) { + $search = Arr::from($search); + } return $caseSensitive ? str_replace($search, '', $subject)