Skip to content

Commit 86dfa58

Browse files
Merge pull request #735 from spatie/data-serialization
Add better support for serializing data
2 parents 125dd19 + e60e411 commit 86dfa58

13 files changed

+183
-79
lines changed

src/Concerns/BaseData.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public function __sleep(): array
9090
->properties
9191
->map(fn (DataProperty $property) => $property->name)
9292
->when($dataClass->appendable, fn (Collection $properties) => $properties->push('_additional'))
93+
->when(property_exists($this, '_dataContext'), fn (Collection $properties) => $properties->push('_dataContext'))
9394
->toArray();
9495
}
9596
}

src/Lazy.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public static function closure(Closure $closure): ClosureLazy
4444

4545
abstract public function resolve(): mixed;
4646

47+
abstract public function __serialize(): array;
48+
49+
abstract public function __unserialize(array $data): void;
50+
4751
public function defaultIncluded(bool $defaultIncluded = true): self
4852
{
4953
$this->defaultIncluded = $defaultIncluded;

src/Support/Lazy/ConditionalLazy.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Spatie\LaravelData\Support\Lazy;
44

55
use Closure;
6+
use Laravel\SerializableClosure\SerializableClosure;
67
use Spatie\LaravelData\Lazy;
78

89
class ConditionalLazy extends Lazy
@@ -22,4 +23,20 @@ public function shouldBeIncluded(): bool
2223
{
2324
return (bool) ($this->condition)();
2425
}
26+
27+
public function __serialize(): array
28+
{
29+
return [
30+
'condition' => new SerializableClosure($this->condition),
31+
'value' => new SerializableClosure($this->value),
32+
'defaultIncluded' => $this->defaultIncluded,
33+
];
34+
}
35+
36+
public function __unserialize(array $data): void
37+
{
38+
$this->condition = $data['condition']->getClosure();
39+
$this->value = $data['value']->getClosure();
40+
$this->defaultIncluded = $data['defaultIncluded'];
41+
}
2542
}

src/Support/Lazy/DefaultLazy.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Spatie\LaravelData\Support\Lazy;
44

55
use Closure;
6+
use Laravel\SerializableClosure\SerializableClosure;
67
use Spatie\LaravelData\Lazy;
78

89
class DefaultLazy extends Lazy
@@ -16,4 +17,18 @@ public function resolve(): mixed
1617
{
1718
return ($this->value)();
1819
}
20+
21+
public function __serialize(): array
22+
{
23+
return [
24+
'value' => new SerializableClosure($this->value),
25+
'defaultIncluded' => $this->defaultIncluded,
26+
];
27+
}
28+
29+
public function __unserialize(array $data): void
30+
{
31+
$this->value = $data['value']->getClosure();
32+
$this->defaultIncluded = $data['defaultIncluded'];
33+
}
1934
}

src/Support/Lazy/LivewireLostLazy.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,20 @@ public function resolve(): mixed
1717
{
1818
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.");
1919
}
20+
21+
public function __serialize(): array
22+
{
23+
return [
24+
'dataClass' => $this->dataClass,
25+
'propertyName' => $this->propertyName,
26+
'defaultIncluded' => $this->defaultIncluded,
27+
];
28+
}
29+
30+
public function __unserialize(array $data): void
31+
{
32+
$this->dataClass = $data['dataClass'];
33+
$this->propertyName = $data['propertyName'];
34+
$this->defaultIncluded = $data['defaultIncluded'];
35+
}
2036
}

src/Support/Lazy/RelationalLazy.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Closure;
66
use Illuminate\Database\Eloquent\Model;
7+
use Laravel\SerializableClosure\SerializableClosure;
78
use Spatie\LaravelData\Lazy;
89

910
class RelationalLazy extends Lazy
@@ -24,4 +25,22 @@ public function shouldBeIncluded(): bool
2425
{
2526
return $this->model->relationLoaded($this->relation);
2627
}
28+
29+
public function __serialize(): array
30+
{
31+
return [
32+
'relation' => $this->relation,
33+
'model' => $this->model,
34+
'value' => new SerializableClosure($this->value),
35+
'defaultIncluded' => $this->defaultIncluded,
36+
];
37+
}
38+
39+
public function __unserialize(array $data): void
40+
{
41+
$this->relation = $data['relation'];
42+
$this->model = $data['model'];
43+
$this->value = $data['value']->getClosure();
44+
$this->defaultIncluded = $data['defaultIncluded'];
45+
}
2746
}

src/Support/Partials/Partial.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,4 +265,30 @@ public function __toString(): string
265265
{
266266
return implode('.', $this->segments)." (current: {$this->pointer})";
267267
}
268+
269+
public function __serialize(): array
270+
{
271+
return [
272+
'segmentCount' => $this->segmentCount,
273+
'endsInAll' => $this->endsInAll,
274+
'segments' => $this->segments,
275+
'condition' => $this->condition
276+
? serialize(new SerializableClosure($this->condition))
277+
: null,
278+
'permanent' => $this->permanent,
279+
'pointer' => $this->pointer,
280+
];
281+
}
282+
283+
public function __unserialize(array $data): void
284+
{
285+
$this->segmentCount = $data['segmentCount'];
286+
$this->endsInAll = $data['endsInAll'];
287+
$this->segments = $data['segments'];
288+
$this->pointer = $data['pointer'];
289+
$this->condition = $data['condition']
290+
? unserialize($data['condition'])->getClosure()
291+
: null;
292+
$this->permanent = $data['permanent'];
293+
}
268294
}

tests/DataCollectionTest.php

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
use Spatie\LaravelData\Tests\Fakes\LazyData;
1212
use Spatie\LaravelData\Tests\Fakes\SimpleData;
1313

14-
use function Spatie\Snapshots\assertMatchesSnapshot;
15-
1614
it('can filter a collection', function () {
1715
$collection = new DataCollection(SimpleData::class, ['A', 'B']);
1816

@@ -238,35 +236,6 @@
238236
->toMatchArray($filtered);
239237
});
240238

241-
it('can serialize and unserialize a data collection', function () {
242-
$collection = new DataCollection(SimpleData::class, ['A', 'B']);
243-
244-
$serialized = serialize($collection);
245-
246-
assertMatchesSnapshot($serialized);
247-
248-
$unserialized = unserialize($serialized);
249-
250-
expect($unserialized)->toBeInstanceOf(DataCollection::class);
251-
expect($unserialized)->toEqual(new DataCollection(SimpleData::class, ['A', 'B']));
252-
});
253-
254-
it('during the serialization process some properties are thrown away', function () {
255-
$collection = new DataCollection(SimpleData::class, ['A', 'B']);
256-
257-
$collection->include('test');
258-
$collection->exclude('test');
259-
$collection->only('test');
260-
$collection->except('test');
261-
$collection->wrap('test');
262-
263-
$unserialized = unserialize(serialize($collection));
264-
265-
$invaded = invade($unserialized);
266-
267-
expect($invaded->_dataContext)->toBeNull();
268-
});
269-
270239
it('can use a custom collection extended from collection to collect a collection of data objects', function () {
271240
$collection = SimpleData::collect(new CustomCollection([
272241
['string' => 'A'],

tests/DataTest.php

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@
2222
use Spatie\LaravelData\Data;
2323
use Spatie\LaravelData\Dto;
2424
use Spatie\LaravelData\Resource;
25-
use Spatie\LaravelData\Tests\Fakes\SimpleData;
2625
use Spatie\LaravelData\Tests\Fakes\SimpleDto;
2726
use Spatie\LaravelData\Tests\Fakes\SimpleResource;
2827

29-
use function Spatie\Snapshots\assertMatchesSnapshot;
30-
3128
it('also works by using traits and interfaces, skipping the base data class', function () {
3229
$data = new class ('') implements Responsable, AppendableDataContract, BaseDataContract, TransformableDataContract, IncludeableDataContract, ResponsableDataContract, ValidateableDataContract, WrappableDataContract, EmptyDataContract {
3330
use ResponsableData;
@@ -56,51 +53,6 @@ public static function fromString(string $string): static
5653
});
5754

5855

59-
it('can serialize and unserialize a data object', function () {
60-
$object = SimpleData::from('Hello world');
61-
62-
$serialized = serialize($object);
63-
64-
assertMatchesSnapshot($serialized);
65-
66-
$unserialized = unserialize($serialized);
67-
68-
expect($unserialized)->toBeInstanceOf(SimpleData::class);
69-
expect($unserialized->string)->toEqual('Hello world');
70-
});
71-
72-
it('can serialize and unserialize a data object with additional data', function () {
73-
$object = SimpleData::from('Hello world')->additional([
74-
'int' => 69,
75-
]);
76-
77-
$serialized = serialize($object);
78-
79-
assertMatchesSnapshot($serialized);
80-
81-
$unserialized = unserialize($serialized);
82-
83-
expect($unserialized)->toBeInstanceOf(SimpleData::class);
84-
expect($unserialized->string)->toEqual('Hello world');
85-
expect($unserialized->getAdditionalData())->toEqual(['int' => 69]);
86-
});
87-
88-
it('during the serialization process some properties are thrown away', function () {
89-
$object = SimpleData::from('Hello world');
90-
91-
$object->include('test');
92-
$object->exclude('test');
93-
$object->only('test');
94-
$object->except('test');
95-
$object->wrap('test');
96-
97-
$unserialized = unserialize(serialize($object));
98-
99-
$invaded = invade($unserialized);
100-
101-
expect($invaded->_dataContext)->toBeNull();
102-
});
103-
10456
it('can use data as an DTO', function () {
10557
$dto = SimpleDto::from('Hello World');
10658

tests/SerializeableTest.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
4+
use Spatie\LaravelData\DataCollection;
5+
use Spatie\LaravelData\Lazy;
6+
use Spatie\LaravelData\Support\Lazy\DefaultLazy;
7+
use Spatie\LaravelData\Tests\Fakes\LazyData;
8+
use Spatie\LaravelData\Tests\Fakes\SimpleData;
9+
10+
use function Spatie\Snapshots\assertMatchesSnapshot;
11+
12+
it('can serialize and unserialize a data object', function () {
13+
$object = SimpleData::from('Hello world');
14+
15+
$serialized = serialize($object);
16+
17+
assertMatchesSnapshot($serialized);
18+
19+
$unserialized = unserialize($serialized);
20+
21+
expect($unserialized)->toBeInstanceOf(SimpleData::class);
22+
expect($unserialized->string)->toEqual('Hello world');
23+
});
24+
25+
it('can serialize and unserialize a data object with additional data', function () {
26+
$object = SimpleData::from('Hello world')->additional([
27+
'int' => 69,
28+
]);
29+
30+
$serialized = serialize($object);
31+
32+
assertMatchesSnapshot($serialized);
33+
34+
$unserialized = unserialize($serialized);
35+
36+
expect($unserialized)->toBeInstanceOf(SimpleData::class);
37+
expect($unserialized->string)->toEqual('Hello world');
38+
expect($unserialized->getAdditionalData())->toEqual(['int' => 69]);
39+
});
40+
41+
it('can serialize and unserialize a data collection', function () {
42+
$collection = new DataCollection(SimpleData::class, ['A', 'B']);
43+
44+
$serialized = serialize($collection);
45+
46+
assertMatchesSnapshot($serialized);
47+
48+
$unserialized = unserialize($serialized);
49+
50+
expect($unserialized)->toBeInstanceOf(DataCollection::class);
51+
expect($unserialized)->toEqual(new DataCollection(SimpleData::class, ['A', 'B']));
52+
});
53+
54+
it('will keep context attached to data when serialized', function () {
55+
$object = LazyData::from('Hello world')->include('name');
56+
57+
$unserialized = unserialize(serialize($object));
58+
59+
expect($unserialized)->toBeInstanceOf(LazyData::class);
60+
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
61+
});
62+
63+
it('is possible to add partials with closures and serialize them', function () {
64+
$object = LazyData::from('Hello world')->includeWhen(
65+
'name',
66+
fn (LazyData $data) => $data->name instanceof DefaultLazy
67+
);
68+
69+
$unserialized = unserialize(serialize($object));
70+
71+
expect($unserialized)->toBeInstanceOf(LazyData::class);
72+
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
73+
});
74+
75+
it('is possible to serialize conditional lazy properties', function () {
76+
$object = new LazyData(Lazy::when(
77+
fn () => true,
78+
fn () => 'Hello world'
79+
));
80+
81+
$unserialized = unserialize(serialize($object));
82+
83+
expect($unserialized)->toBeInstanceOf(LazyData::class);
84+
expect($unserialized->toArray())->toMatchArray(['name' => 'Hello world']);
85+
});

0 commit comments

Comments
 (0)