diff --git a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php index e36b13df2184..b520b6f55525 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php @@ -2,11 +2,13 @@ namespace Illuminate\Database\Eloquent\Casts; +use Closure; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Support\Collection; use Illuminate\Support\Str; use InvalidArgumentException; +use Laravel\SerializableClosure\SerializableClosure; class AsCollection implements Castable { @@ -51,6 +53,10 @@ public function get($model, $key, $value, $attributes) if (is_string($this->arguments[1])) { $this->arguments[1] = Str::parseCallback($this->arguments[1]); + + if (! class_exists($this->arguments[1][0])) { + $this->arguments[1] = @unserialize($this->arguments[1][0])->getClosure(); + } } return is_callable($this->arguments[1]) @@ -68,7 +74,7 @@ public function set($model, $key, $value, $attributes) /** * Specify the type of object each item in the collection should be mapped to. * - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|Closure $map * @return string */ public static function of($map) @@ -80,13 +86,15 @@ public static function of($map) * Specify the collection type for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|Closure|null $map * @return string */ public static function using($class, $map = null) { if (is_array($map) && is_callable($map)) { $map = $map[0].'@'.$map[1]; + } elseif ($map instanceof Closure) { + $map = serialize(new SerializableClosure($map)); } return static::class.':'.implode(',', [$class, $map]); diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index b5912fa20b10..680ab19a0e37 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -2,12 +2,14 @@ namespace Illuminate\Database\Eloquent\Casts; +use Closure; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Str; use InvalidArgumentException; +use Laravel\SerializableClosure\SerializableClosure; class AsEncryptedCollection implements Castable { @@ -46,6 +48,10 @@ public function get($model, $key, $value, $attributes) if (is_string($this->arguments[1])) { $this->arguments[1] = Str::parseCallback($this->arguments[1]); + + if (! class_exists($this->arguments[1][0])) { + $this->arguments[1] = @unserialize($this->arguments[1][0])->getClosure(); + } } return is_callable($this->arguments[1]) @@ -67,7 +73,7 @@ public function set($model, $key, $value, $attributes) /** * Specify the type of object each item in the collection should be mapped to. * - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|Closure $map * @return string */ public static function of($map) @@ -79,13 +85,15 @@ public static function of($map) * Specify the collection for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|Closure|null $map * @return string */ public static function using($class, $map = null) { if (is_array($map) && is_callable($map)) { $map = $map[0].'@'.$map[1]; + } elseif ($map instanceof Closure) { + $map = serialize(new SerializableClosure($map)); } return static::class.':'.implode(',', [$class, $map]); diff --git a/tests/Integration/Database/DatabaseCustomCastsTest.php b/tests/Integration/Database/DatabaseCustomCastsTest.php index ec14a2bf3c49..b7cb84ba3287 100644 --- a/tests/Integration/Database/DatabaseCustomCastsTest.php +++ b/tests/Integration/Database/DatabaseCustomCastsTest.php @@ -215,6 +215,37 @@ public function test_as_custom_collection_with_map_callback(): void $this->assertInstanceOf(FluentWithCallback::class, $model->collection->first()); $this->assertSame('bar', $model->collection->first()->foo); } + + public function test_as_collection_with_map_using_closure() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::of(fn ($data) => new Fluent($data)), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(Fluent::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } + + public function test_as_custom_collection_with_map_using_closure() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::using(CustomCollection::class, fn ($data) => new Fluent($data)), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(CustomCollection::class, $model->collection); + $this->assertInstanceOf(Fluent::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } } class TestEloquentModelWithCustomCasts extends Model