diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php index bd79380b0fb..c11b3252a0f 100644 --- a/src/LiveComponent/src/LiveComponentHydrator.php +++ b/src/LiveComponent/src/LiveComponentHydrator.php @@ -18,10 +18,12 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\Exception\HydrationException; @@ -245,6 +247,7 @@ public function hydrateValue(mixed $value, LivePropMetadata $propMetadata, objec return $parentObject->{$propMetadata->hydrateMethod()}($value); } + $collectionValueType = $propMetadata->collectionValueType(); if ($propMetadata->useSerializerForHydration()) { if (!interface_exists(DenormalizerInterface::class)) { throw new \LogicException(sprintf('The LiveProp "%s" on component "%s" has "useSerializerForHydration: true", but the Serializer component is not installed. Try running "composer require symfony/serializer".', $propMetadata->getName(), $parentObject::class)); @@ -256,10 +259,12 @@ public function hydrateValue(mixed $value, LivePropMetadata $propMetadata, objec throw new \LogicException(sprintf('The LiveProp "%s" on component "%s" has "useSerializerForHydration: true", but the given serializer does not implement DenormalizerInterface.', $propMetadata->getName(), $parentObject::class)); } - if ($propMetadata->collectionValueType()) { - $builtInType = $propMetadata->collectionValueType()->getBuiltinType(); - if (Type::BUILTIN_TYPE_OBJECT === $builtInType) { - $type = $propMetadata->collectionValueType()->getClassName().'[]'; + if ($collectionValueType instanceof BuiltinType || $collectionValueType instanceof ObjectType) { + $type = $collectionValueType->asNonNullable().'[]'; + } elseif ($collectionValueType instanceof LegacyType) { + $builtInType = $collectionValueType->getBuiltinType(); + if (LegacyType::BUILTIN_TYPE_OBJECT === $builtInType) { + $type = $collectionValueType->getClassName().'[]'; } else { $type = $builtInType.'[]'; } @@ -274,8 +279,10 @@ public function hydrateValue(mixed $value, LivePropMetadata $propMetadata, objec return $this->serializer->denormalize($value, $type, 'json', $propMetadata->serializationContext()); } - if ($propMetadata->collectionValueType() && Type::BUILTIN_TYPE_OBJECT === $propMetadata->collectionValueType()->getBuiltinType()) { - $collectionClass = $propMetadata->collectionValueType()->getClassName(); + if ($collectionValueType instanceof ObjectType + || $collectionValueType instanceof LegacyType && LegacyType::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType() + ) { + $collectionClass = $collectionValueType->getClassName(); foreach ($value as $key => $objectItem) { $value[$key] = $this->hydrateObjectValue($objectItem, $collectionClass, true, $propMetadata->getFormat(), $parentObject::class, sprintf('%s.%s', $propMetadata->getName(), $key), $parentObject); } @@ -448,8 +455,11 @@ private function dehydrateValue(mixed $value, LivePropMetadata $propMetadata, ob } if (\is_array($value)) { - if ($propMetadata->collectionValueType() && Type::BUILTIN_TYPE_OBJECT === $propMetadata->collectionValueType()->getBuiltinType()) { - $collectionClass = $propMetadata->collectionValueType()->getClassName(); + $collectionValueType = $propMetadata->collectionValueType(); + if ($collectionValueType instanceof ObjectType + || $collectionValueType instanceof LegacyType && LegacyType::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType() + ) { + $collectionClass = $collectionValueType->getClassName(); foreach ($value as $key => $objectItem) { if (!$objectItem instanceof $collectionClass) { throw new \LogicException(sprintf('The LiveProp "%s" on component "%s" is an array. We determined the array is full of %s objects, but at least on key had a different value of %s', $propMetadata->getName(), $parentObject::class, $collectionClass, get_debug_type($objectItem))); diff --git a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php index 37c68f1a2dd..f03edaf287f 100644 --- a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php +++ b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php @@ -12,7 +12,11 @@ namespace Symfony\UX\LiveComponent\Metadata; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\UnionType; use Symfony\Contracts\Service\ResetInterface; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\TwigComponent\ComponentFactory; @@ -79,26 +83,42 @@ public function createLivePropMetadata(string $className, string $propertyName, throw new \LogicException(sprintf('Union or intersection types are not supported for LiveProps. You may want to change the type of property %s in %s.', $property->getName(), $property->getDeclaringClass()->getName())); } - $infoTypes = $this->propertyTypeExtractor->getTypes($className, $propertyName) ?? []; + if (method_exists($this->propertyTypeExtractor, 'getType')) { + $infoType = $this->propertyTypeExtractor->getType($className, $propertyName); + $isTypeBuiltIn = $type?->isBuiltin() ?? ($infoType instanceof BuiltinType); + $isTypeNullable = $type?->allowsNull() ?? $infoType?->isNullable() ?? true; - $collectionValueType = null; - foreach ($infoTypes as $infoType) { - if ($infoType->isCollection()) { - foreach ($infoType->getCollectionValueTypes() as $valueType) { - $collectionValueType = $valueType; - break; + $infoType = $infoType?->asNonNullable(); + + $collectionValueType = null; + if ($infoType instanceof CollectionType) { + $collectionValueType = $infoType->getCollectionValueType()->asNonNullable(); + if ($collectionValueType instanceof UnionType || $collectionValueType instanceof IntersectionType) { + [$collectionValueType] = $collectionValueType->getTypes(); } } - } - - if (null === $type && null === $collectionValueType && isset($infoTypes[0])) { - $infoType = Type::BUILTIN_TYPE_OBJECT === $infoTypes[0]->getBuiltinType() ? $infoTypes[0]->getClassName() : $infoTypes[0]->getBuiltinType(); - $isTypeBuiltIn = null === $infoTypes[0]->getClassName(); - $isTypeNullable = $infoTypes[0]->isNullable(); } else { - $infoType = $type?->getName(); - $isTypeBuiltIn = $type?->isBuiltin() ?? false; - $isTypeNullable = $type?->allowsNull() ?? true; + $infoTypes = $this->propertyTypeExtractor->getTypes($className, $propertyName) ?? []; + + $collectionValueType = null; + foreach ($infoTypes as $infoType) { + if ($infoType->isCollection()) { + foreach ($infoType->getCollectionValueTypes() as $valueType) { + $collectionValueType = $valueType; + break; + } + } + } + + if (null === $type && null === $collectionValueType && isset($infoTypes[0])) { + $infoType = LegacyType::BUILTIN_TYPE_OBJECT === $infoTypes[0]->getBuiltinType() ? $infoTypes[0]->getClassName() : $infoTypes[0]->getBuiltinType(); + $isTypeBuiltIn = null === $infoTypes[0]->getClassName(); + $isTypeNullable = $infoTypes[0]->isNullable(); + } else { + $infoType = $type?->getName(); + $isTypeBuiltIn = $type?->isBuiltin() ?? false; + $isTypeNullable = $type?->allowsNull() ?? true; + } } return new LivePropMetadata( diff --git a/src/LiveComponent/src/Metadata/LivePropMetadata.php b/src/LiveComponent/src/Metadata/LivePropMetadata.php index 1878a159b03..efeeb38d352 100644 --- a/src/LiveComponent/src/Metadata/LivePropMetadata.php +++ b/src/LiveComponent/src/Metadata/LivePropMetadata.php @@ -11,7 +11,8 @@ namespace Symfony\UX\LiveComponent\Metadata; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; use Symfony\UX\LiveComponent\Attribute\LiveProp; /** @@ -27,7 +28,7 @@ public function __construct( private ?string $typeName, private bool $isBuiltIn, private bool $allowsNull, - private ?Type $collectionValueType, + private Type|LegacyType|null $collectionValueType, ) { } @@ -99,7 +100,7 @@ public function serializationContext(): array return $this->liveProp->serializationContext(); } - public function collectionValueType(): ?Type + public function collectionValueType(): Type|LegacyType|null { return $this->collectionValueType; }