diff --git a/composer.json b/composer.json index ac0622a91..65f4066b3 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "spatie/php-structure-discoverer": "^2.0" }, "require-dev" : { + "livewire/livewire" : "^3.0", "fakerphp/faker": "^1.14", "friendsofphp/php-cs-fixer": "^3.0", "inertiajs/inertia-laravel": "dev-master#4508fd1", diff --git a/src/LaravelDataServiceProvider.php b/src/LaravelDataServiceProvider.php index f748b0eef..d9b648af2 100644 --- a/src/LaravelDataServiceProvider.php +++ b/src/LaravelDataServiceProvider.php @@ -2,11 +2,13 @@ namespace Spatie\LaravelData; +use Livewire\Livewire; use Spatie\LaravelData\Commands\DataMakeCommand; use Spatie\LaravelData\Commands\DataStructuresCacheCommand; use Spatie\LaravelData\Contracts\BaseData; use Spatie\LaravelData\Support\Caching\DataStructureCache; use Spatie\LaravelData\Support\DataConfig; +use Spatie\LaravelData\Support\Livewire\LivewireDataSynth; use Spatie\LaravelData\Support\VarDumper\VarDumperManager; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -50,6 +52,15 @@ function () { fn ($container) => $class::from($container['request']) ); }); + + if(class_exists(Livewire::class)) { + $this->registerLivewireSynths(); + } + } + + protected function registerLivewireSynths(): void + { + Livewire::propertySynthesizer(LivewireDataSynth::class); } public function packageBooted(): void diff --git a/src/Support/Lazy/LivewireLostLazy.php b/src/Support/Lazy/LivewireLostLazy.php new file mode 100644 index 000000000..2e9452419 --- /dev/null +++ b/src/Support/Lazy/LivewireLostLazy.php @@ -0,0 +1,20 @@ +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."); + } +} diff --git a/src/Support/Livewire/LivewireDataSynth.php b/src/Support/Livewire/LivewireDataSynth.php new file mode 100644 index 000000000..78b306dd1 --- /dev/null +++ b/src/Support/Livewire/LivewireDataSynth.php @@ -0,0 +1,119 @@ + probably a better default + // What if we want to create a new data object and don't have the lazyvalue + // -> we could create a new LiveWireMissingLazy object, which we then use as the lazy value + // -> when resolving it would throw an error saying the value was lost in LiveWire + // + // Problem with computed properties should be sorted out + // + // DataCollections synth from the PR + // + // Do we want livewire as an dependency? + // + // Update docs + // + // Can we test this? + // + // Mapping property names, should we do this? + + + protected DataConfig $dataConfig; + + public static string $key = 'laravel-data-object'; + + public function __construct(ComponentContext $context, $path) + { + $this->dataConfig = app(DataConfig::class); + + parent::__construct($context, $path); + } + + public static function match($target) + { + return $target instanceof BaseData && $target instanceof TransformableData; + } + + public function get(&$target, $key): BaseData + { + return $target->{$key}; + } + + public function set(&$target, $key, $value): void + { + $target->{$key} = $value; + } + + /** + * @param BaseData&TransformableData $target + * @param callable(mixed):mixed $dehydrateChild + * + * @return array + */ + public function dehydrate( + BaseData&TransformableData $target, + callable $dehydrateChild + ): array { + $morph = $this->dataConfig->morphMap->getDataClassAlias($target::class) ?? $target::class; + + $payload = $target->all(); + + ray($payload); + + foreach ($payload as $key => $value) { + $payload[$key] = $dehydrateChild($key, $value); + } + + return [ + $payload, + ['morph' => $morph], + ]; + } + + /** + * @param mixed $value + * @param array $meta + * @param callable(mixed):mixed $hydrateChild + * + * @return BaseData + */ + public function hydrate( + array $value, + array $meta, + callable $hydrateChild + ): BaseData { + $morph = $meta['morph']; + + $dataClass = $this->dataConfig->morphMap->getMorphedDataClass($morph) ?? $morph; + + $payload = []; + + foreach ($this->dataConfig->getDataClass($dataClass)->properties as $name => $property) { + if (array_key_exists($name, $value) === false && $property->type->lazyType) { + $payload[$name] = new LivewireLostLazy($dataClass, $name); + + continue; + } + + $payload[$name] = $hydrateChild($name, $value[$name]); + } + + return $dataClass::factory() + ->ignoreMagicalMethod('fromLivewire') + ->from($payload); + } +}