Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LiveComponent] Implement hydratation of DTO object #1090

Merged
merged 11 commits into from
Sep 22, 2023
46 changes: 40 additions & 6 deletions src/LiveComponent/src/LiveComponentHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\Exception\HydrationException;
use Symfony\UX\LiveComponent\Hydration\HydrationExtensionInterface;
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadata;
Expand Down Expand Up @@ -373,10 +375,10 @@ private function dehydrateValue(mixed $value, LivePropMetadata $propMetadata, ob
// at this point, we have an object and can assume $propMetadata->getType()
// is set correctly (needed for hydration later)

return $this->dehydrateObjectValue($value, $propMetadata->getType(), $propMetadata->getFormat(), $component::class, $propMetadata->getName());
return $this->dehydrateObjectValue($value, $propMetadata->getType(), $propMetadata->getFormat(), $component);
}

private function dehydrateObjectValue(object $value, string $classType, ?string $dateFormat, string $componentClassForError, string $propertyPathForError): mixed
private function dehydrateObjectValue(object $value, string $classType, ?string $dateFormat, object $component): mixed
{
if ($value instanceof \DateTimeInterface) {
return $value->format($dateFormat ?: \DateTimeInterface::RFC3339);
Expand All @@ -392,7 +394,22 @@ private function dehydrateObjectValue(object $value, string $classType, ?string
}
}

throw new \LogicException(sprintf('Unable to dehydrate value of type "%s" for property "%s" on component "%s". Either (1) change this to a simpler value, (2) add the hydrateWith/dehydrateWith options to LiveProp or (3) set "useSerializerForHydration: true" on the LiveProp.', $value::class, $propertyPathForError, $componentClassForError));
$reflexionExtractor = new ReflectionExtractor();
$properties = $reflexionExtractor->getProperties($classType);
$propertiesValues = [];
foreach ($properties as $property) {
if ($reflexionExtractor->isReadable($classType, $property)) {
$propertyValue = $this->propertyAccessor->getValue($value, $property);
$type = $reflexionExtractor->getTypes($classType, $property)[0]->getBuiltinType();
if ($type === 'object') {
$type = $reflexionExtractor->getTypes($classType, $property)[0]->getClassName();
}
$propMetadata = new LivePropMetadata($property, new LiveProp(true), $type, false, true, null);
$propertiesValues[$property] = $this->dehydrateValue($propertyValue, $propMetadata, $component);
}
}
weaverryan marked this conversation as resolved.
Show resolved Hide resolved

return $propertiesValues;
}

private function hydrateValue(mixed $value, LivePropMetadata $propMetadata, object $component): mixed
Expand All @@ -412,7 +429,7 @@ private function hydrateValue(mixed $value, LivePropMetadata $propMetadata, obje
if ($propMetadata->collectionValueType() && Type::BUILTIN_TYPE_OBJECT === $propMetadata->collectionValueType()->getBuiltinType()) {
$collectionClass = $propMetadata->collectionValueType()->getClassName();
foreach ($value as $key => $objectItem) {
$value[$key] = $this->hydrateObjectValue($objectItem, $collectionClass, true, $component::class, sprintf('%s.%s', $propMetadata->getName(), $key));
$value[$key] = $this->hydrateObjectValue($objectItem, $collectionClass, true, $component::class, sprintf('%s.%s', $propMetadata->getName(), $key), $component);
}
}

Expand All @@ -434,10 +451,10 @@ private function hydrateValue(mixed $value, LivePropMetadata $propMetadata, obje
return $value;
}

return $this->hydrateObjectValue($value, $propMetadata->getType(), $propMetadata->allowsNull(), $component::class, $propMetadata->getName());
return $this->hydrateObjectValue($value, $propMetadata->getType(), $propMetadata->allowsNull(), $component::class, $propMetadata->getName(), $component);
}

private function hydrateObjectValue(mixed $value, string $className, bool $allowsNull, string $componentClassForError, string $propertyPathForError): ?object
private function hydrateObjectValue(mixed $value, string $className, bool $allowsNull, string $componentClassForError, string $propertyPathForError, object $component): ?object
{
// enum
if (is_a($className, \BackedEnum::class, true)) {
Expand Down Expand Up @@ -467,6 +484,23 @@ private function hydrateObjectValue(mixed $value, string $className, bool $allow
}
}

if (is_array($value)) {
$object = new $className;
$extractor = new ReflectionExtractor();
foreach ($value as $property => $propertyValue) {
$type = $extractor->getTypes($className, $property)[0]->getBuiltinType();
$buildIn = true;
if ($type === 'object') {
$type = $extractor->getTypes($className, $property)[0]->getClassName();
$buildIn = false;
}
$propMetadata = new LivePropMetadata($property, new LiveProp(true), $type, $buildIn, true, null);
$this->propertyAccessor->setValue($object, $property, $this->hydrateValue($propertyValue, $propMetadata, $component));
}

return $object;
}

throw new HydrationException(sprintf('Unable to hydrate value of type "%s" for property "%s" on component "%s". Change this to a simpler value, add the hydrateWith/dehydrateWith options to LiveProp or set "useSerializerForHydration: true" on the LiveProp to use the serializer..', $className, $propertyPathForError, $componentClassForError));
WebMamba marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down