Skip to content

Commit

Permalink
Fix an issue where anonymous classes in castables were serialized
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jan 3, 2025
1 parent 30242c4 commit 088b4fd
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/Attributes/WithCastable.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Attribute;
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Casts\Castable;
use Spatie\LaravelData\Casts\CastableCast;
use Spatie\LaravelData\Exceptions\CannotCreateCastAttribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
Expand All @@ -26,6 +27,9 @@ public function __construct(

public function get(): Cast
{
return $this->castableClass::dataCastUsing(...$this->arguments);
return new CastableCast(
$this->castableClass,
$this->arguments
);
}
}
43 changes: 43 additions & 0 deletions src/Casts/CastableCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Spatie\LaravelData\Casts;

use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataProperty;

class CastableCast implements Cast
{
protected Cast $cast;

/**
* @param class-string<\Spatie\LaravelData\Casts\Castable> $castableClass
*/
public function __construct(
public string $castableClass,
public array $arguments
) {
}

public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed
{
if (! isset($this->cast)) {
$this->cast = $this->castableClass::dataCastUsing(...$this->arguments);
}

return $this->cast->cast($property, $value, $properties, $context);
}

public function __serialize(): array
{
return [
'castableClass' => $this->castableClass,
'arguments' => $this->arguments,
];
}

public function __unserialize(array $data): void
{
$this->castableClass = $data['castableClass'];
$this->arguments = $data['arguments'];
}
}
1 change: 0 additions & 1 deletion src/Support/Factories/DataClassFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public function __construct(
) {
}


public function build(ReflectionClass $reflectionClass): DataClass
{
/** @var class-string<Data> $name */
Expand Down
32 changes: 32 additions & 0 deletions tests/Support/Caching/CachedDataConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Mockery\MockInterface;
use Spatie\LaravelData\Attributes\WithCastable;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Support\Caching\CachedDataConfig;
use Spatie\LaravelData\Support\Caching\DataStructureCache;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Tests\Factories\FakeDataStructureFactory;
use Spatie\LaravelData\Tests\Fakes\Castables\SimpleCastable;
use Spatie\LaravelData\Tests\Fakes\SimpleData;

function ensureDataWillBeCached()
Expand Down Expand Up @@ -104,3 +107,32 @@ function (MockInterface $spy) use ($dataClass) {

cache()->get('something-just-to-test-the-mock');
});

it('is possible to cache data classes with castables using anonymous classes', function () {
ensureDataWillBeCached();

$objectDefinition = new class () extends Data {
#[WithCastable(SimpleCastable::class, normalize: true)]
public SimpleCastable $string;
};

$dataClass = app(DataConfig::class)->getDataClass($objectDefinition::class);

expect(isset(invade($dataClass->properties['string']->cast)->cast))->toBeFalse();

$objectDefinition::from(['string' => 'Hello world']);

$dataClass = app(DataConfig::class)->getDataClass($objectDefinition::class);

$reflection = new ReflectionClass(invade($dataClass->properties['string']->cast)->cast);

expect($reflection->isAnonymous())->toBeTrue();

$dataClass->prepareForCache();

app(DataStructureCache::class)->storeDataClass($dataClass);

$newDataClass = app(DataStructureCache::class)->getDataClass($objectDefinition::class);

expect(isset(invade($newDataClass->properties['string']->cast)->cast))->toBeFalse();
});

0 comments on commit 088b4fd

Please sign in to comment.