Skip to content

Commit

Permalink
Add support for serializing data
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Apr 4, 2024
1 parent 3d2cc18 commit e60e411
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 79 deletions.
1 change: 1 addition & 0 deletions src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public function __sleep(): array
->properties
->map(fn (DataProperty $property) => $property->name)
->when($dataClass->appendable, fn (Collection $properties) => $properties->push('_additional'))
->when(property_exists($this, '_dataContext'), fn (Collection $properties) => $properties->push('_dataContext'))
->toArray();
}
}
4 changes: 4 additions & 0 deletions src/Lazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public static function closure(Closure $closure): ClosureLazy

abstract public function resolve(): mixed;

abstract public function __serialize(): array;

abstract public function __unserialize(array $data): void;

public function defaultIncluded(bool $defaultIncluded = true): self
{
$this->defaultIncluded = $defaultIncluded;
Expand Down
17 changes: 17 additions & 0 deletions src/Support/Lazy/ConditionalLazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Support\Lazy;

use Closure;
use Laravel\SerializableClosure\SerializableClosure;
use Spatie\LaravelData\Lazy;

class ConditionalLazy extends Lazy
Expand All @@ -22,4 +23,20 @@ public function shouldBeIncluded(): bool
{
return (bool) ($this->condition)();
}

public function __serialize(): array
{
return [
'condition' => new SerializableClosure($this->condition),
'value' => new SerializableClosure($this->value),
'defaultIncluded' => $this->defaultIncluded,
];
}

public function __unserialize(array $data): void
{
$this->condition = $data['condition']->getClosure();
$this->value = $data['value']->getClosure();
$this->defaultIncluded = $data['defaultIncluded'];
}
}
15 changes: 15 additions & 0 deletions src/Support/Lazy/DefaultLazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Support\Lazy;

use Closure;
use Laravel\SerializableClosure\SerializableClosure;
use Spatie\LaravelData\Lazy;

class DefaultLazy extends Lazy
Expand All @@ -16,4 +17,18 @@ public function resolve(): mixed
{
return ($this->value)();
}

public function __serialize(): array
{
return [
'value' => new SerializableClosure($this->value),
'defaultIncluded' => $this->defaultIncluded,
];
}

public function __unserialize(array $data): void
{
$this->value = $data['value']->getClosure();
$this->defaultIncluded = $data['defaultIncluded'];
}
}
16 changes: 16 additions & 0 deletions src/Support/Lazy/LivewireLostLazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,20 @@ public function resolve(): mixed
{
return throw new Exception("Lazy property `{$this->dataClass}::{$this->propertyName}` was lost when the data object was transformed to be used by Livewire. You can include the property and then the correct value will be set when creating the data object from Livewire again.");
}

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

public function __unserialize(array $data): void
{
$this->dataClass = $data['dataClass'];
$this->propertyName = $data['propertyName'];
$this->defaultIncluded = $data['defaultIncluded'];
}
}
19 changes: 19 additions & 0 deletions src/Support/Lazy/RelationalLazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use Illuminate\Database\Eloquent\Model;
use Laravel\SerializableClosure\SerializableClosure;
use Spatie\LaravelData\Lazy;

class RelationalLazy extends Lazy
Expand All @@ -24,4 +25,22 @@ public function shouldBeIncluded(): bool
{
return $this->model->relationLoaded($this->relation);
}

public function __serialize(): array
{
return [
'relation' => $this->relation,
'model' => $this->model,
'value' => new SerializableClosure($this->value),
'defaultIncluded' => $this->defaultIncluded,
];
}

public function __unserialize(array $data): void
{
$this->relation = $data['relation'];
$this->model = $data['model'];
$this->value = $data['value']->getClosure();
$this->defaultIncluded = $data['defaultIncluded'];
}
}
26 changes: 26 additions & 0 deletions src/Support/Partials/Partial.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,30 @@ public function __toString(): string
{
return implode('.', $this->segments)." (current: {$this->pointer})";
}

public function __serialize(): array
{
return [
'segmentCount' => $this->segmentCount,
'endsInAll' => $this->endsInAll,
'segments' => $this->segments,
'condition' => $this->condition
? serialize(new SerializableClosure($this->condition))
: null,
'permanent' => $this->permanent,
'pointer' => $this->pointer,
];
}

public function __unserialize(array $data): void
{
$this->segmentCount = $data['segmentCount'];
$this->endsInAll = $data['endsInAll'];
$this->segments = $data['segments'];
$this->pointer = $data['pointer'];
$this->condition = $data['condition']
? unserialize($data['condition'])->getClosure()
: null;
$this->permanent = $data['permanent'];
}
}
31 changes: 0 additions & 31 deletions tests/DataCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
use Spatie\LaravelData\Tests\Fakes\LazyData;
use Spatie\LaravelData\Tests\Fakes\SimpleData;

use function Spatie\Snapshots\assertMatchesSnapshot;

it('can filter a collection', function () {
$collection = new DataCollection(SimpleData::class, ['A', 'B']);

Expand Down Expand Up @@ -237,35 +235,6 @@
->toMatchArray($filtered);
});

it('can serialize and unserialize a data collection', function () {
$collection = new DataCollection(SimpleData::class, ['A', 'B']);

$serialized = serialize($collection);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(DataCollection::class);
expect($unserialized)->toEqual(new DataCollection(SimpleData::class, ['A', 'B']));
});

it('during the serialization process some properties are thrown away', function () {
$collection = new DataCollection(SimpleData::class, ['A', 'B']);

$collection->include('test');
$collection->exclude('test');
$collection->only('test');
$collection->except('test');
$collection->wrap('test');

$unserialized = unserialize(serialize($collection));

$invaded = invade($unserialized);

expect($invaded->_dataContext)->toBeNull();
});

it('can use a custom collection extended from collection to collect a collection of data objects', function () {
$collection = SimpleData::collect(new CustomCollection([
['string' => 'A'],
Expand Down
48 changes: 0 additions & 48 deletions tests/DataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Dto;
use Spatie\LaravelData\Resource;
use Spatie\LaravelData\Tests\Fakes\SimpleData;
use Spatie\LaravelData\Tests\Fakes\SimpleDto;
use Spatie\LaravelData\Tests\Fakes\SimpleResource;

use function Spatie\Snapshots\assertMatchesSnapshot;

it('also works by using traits and interfaces, skipping the base data class', function () {
$data = new class ('') implements Responsable, AppendableDataContract, BaseDataContract, TransformableDataContract, IncludeableDataContract, ResponsableDataContract, ValidateableDataContract, WrappableDataContract, EmptyDataContract {
use ResponsableData;
Expand Down Expand Up @@ -56,51 +53,6 @@ public static function fromString(string $string): static
});


it('can serialize and unserialize a data object', function () {
$object = SimpleData::from('Hello world');

$serialized = serialize($object);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(SimpleData::class);
expect($unserialized->string)->toEqual('Hello world');
});

it('can serialize and unserialize a data object with additional data', function () {
$object = SimpleData::from('Hello world')->additional([
'int' => 69,
]);

$serialized = serialize($object);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(SimpleData::class);
expect($unserialized->string)->toEqual('Hello world');
expect($unserialized->getAdditionalData())->toEqual(['int' => 69]);
});

it('during the serialization process some properties are thrown away', function () {
$object = SimpleData::from('Hello world');

$object->include('test');
$object->exclude('test');
$object->only('test');
$object->except('test');
$object->wrap('test');

$unserialized = unserialize(serialize($object));

$invaded = invade($unserialized);

expect($invaded->_dataContext)->toBeNull();
});

it('can use data as an DTO', function () {
$dto = SimpleDto::from('Hello World');

Expand Down
85 changes: 85 additions & 0 deletions tests/SerializeableTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php


use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Support\Lazy\DefaultLazy;
use Spatie\LaravelData\Tests\Fakes\LazyData;
use Spatie\LaravelData\Tests\Fakes\SimpleData;

use function Spatie\Snapshots\assertMatchesSnapshot;

it('can serialize and unserialize a data object', function () {
$object = SimpleData::from('Hello world');

$serialized = serialize($object);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(SimpleData::class);
expect($unserialized->string)->toEqual('Hello world');
});

it('can serialize and unserialize a data object with additional data', function () {
$object = SimpleData::from('Hello world')->additional([
'int' => 69,
]);

$serialized = serialize($object);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(SimpleData::class);
expect($unserialized->string)->toEqual('Hello world');
expect($unserialized->getAdditionalData())->toEqual(['int' => 69]);
});

it('can serialize and unserialize a data collection', function () {
$collection = new DataCollection(SimpleData::class, ['A', 'B']);

$serialized = serialize($collection);

assertMatchesSnapshot($serialized);

$unserialized = unserialize($serialized);

expect($unserialized)->toBeInstanceOf(DataCollection::class);
expect($unserialized)->toEqual(new DataCollection(SimpleData::class, ['A', 'B']));
});

it('will keep context attached to data when serialized', function () {
$object = LazyData::from('Hello world')->include('name');

$unserialized = unserialize(serialize($object));

expect($unserialized)->toBeInstanceOf(LazyData::class);
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
});

it('is possible to add partials with closures and serialize them', function () {
$object = LazyData::from('Hello world')->includeWhen(
'name',
fn (LazyData $data) => $data->name instanceof DefaultLazy
);

$unserialized = unserialize(serialize($object));

expect($unserialized)->toBeInstanceOf(LazyData::class);
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
});

it('is possible to serialize conditional lazy properties', function () {
$object = new LazyData(Lazy::when(
fn () => true,
fn () => 'Hello world'
));

$unserialized = unserialize(serialize($object));

expect($unserialized)->toBeInstanceOf(LazyData::class);
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
some_camel_case_property: string;
'some:non:standard:property': string;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit e60e411

Please sign in to comment.