Skip to content

Conversation

@Kocal
Copy link
Member

@Kocal Kocal commented Jul 13, 2025

Q A
Bug fix? yes
New feature? no
Docs? no
Issues Fix #2888
License MIT

Prevent:

1) Symfony\UX\LiveComponent\Tests\Functional\Form\ComponentWithFormTest::testFormWithLivePropContainingAnEntityImplementingAnInterface LogicException: Cannot dehydrate value typed as interface "Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User" on component "Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormWithUserInterfaceComponent". Change this to a concrete type that can be dehydrated. Or set the hydrateWith/dehydrateWith options in LiveProp or set "useSerializerForHydration: true" on the LiveProp to use the serializer. 

Given the LiveComponent:

<?php /*  * This file is part of the Symfony package.  *  * (c) Fabien Potencier <fabien@symfony.com>  *  * For the full copyright and license information, please view the LICENSE  * file that was distributed with this source code.  */ namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\ComponentWithFormTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User; use Symfony\UX\LiveComponent\Tests\Fixtures\Form\UserFormType; #[AsLiveComponent('form_with_user_interface', template: 'components/form_with_user_interface.html.twig')] class FormWithUserInterfaceComponent extends AbstractController { use ComponentWithFormTrait; use DefaultActionTrait; #[LiveProp] public User $user; protected function instantiateForm(): FormInterface { return $this->createForm(UserFormType::class, $this->user); } }

Dumping $propMetadata in \Symfony\UX\LiveComponent\LiveComponentHydrator::dehydrateValue gives:

Symfony\UX\LiveComponent\Metadata\LivePropMetadata {#240 -name: "user" -liveProp: Symfony\UX\LiveComponent\Attribute\LiveProp {#230 -writable: false -hydrateWith: null -dehydrateWith: null -useSerializerForHydration: false -serializationContext: [] -fieldName: null -format: null -updateFromParent: false -onUpdated: null -url: false -modifier: null } -type: Symfony\Component\TypeInfo\Type\NullableType {#4197 -types: array:2 [ 0 => Symfony\Component\TypeInfo\Type\ObjectType {#5811 -className: "Symfony\Component\Security\Core\User\UserInterface" } 1 => Symfony\Component\TypeInfo\Type\BuiltinType {#4208 -typeIdentifier: Symfony\Component\TypeInfo\TypeIdentifier {#5983 +name: "NULL" +value: "null" } } ] -type: Symfony\Component\TypeInfo\Type\ObjectType {#5811} } } 

The class name is the UserInterface and not User 🤔

To tests it

  • With PropertyInfo only: sfcp req symfony/property-info:^6.4 -W && sfp vendor/bin/simple-phpunit tests/Functional/Form/ComponentWithFormTest.php --filter "testFormWithLivePropContainingAnEntityImplementingAnInterface"
  • With PropertyInfo & Type: sfcp req 'symfony/property-info:7.2.*' 'symfony/type-info:7.2.*' && sfp vendor/bin/simple-phpunit tests/Functional/Form/ComponentWithFormTest.php --filter "testFormWithLivePropContainingAnEntityImplementingAnInterface"
@carsonbot carsonbot added Bug Bug Fix LiveComponent Status: Needs Review Needs to be reviewed labels Jul 13, 2025
@Kocal Kocal marked this pull request as draft July 13, 2025 13:05
@Kocal Kocal force-pushed the 2888-live-hydrate-interface branch 2 times, most recently from ee49115 to d071359 Compare July 13, 2025 13:32
@Kocal
Copy link
Member Author

Kocal commented Jul 13, 2025

After some investigations, it happens because of PhpStanExtractor from Symfony PropertyInfo.

When trying to resolve the type of FormWithUserInterfaceComponent::$user, it will instead resolve the type of AbstractController::getUser() which returns ?UserInterface.

@Kocal

This comment was marked as outdated.

@Kocal
Copy link
Member Author

Kocal commented Jul 14, 2025

After more investigations, I don't think it's related to PropertyInfo itself (PropertyInfo 6.4 also returns type for AbstractController::getUser()) but because of the extra logic that use the native ReflectionType if possible:
image

@Kocal Kocal changed the title [LiveComponent] Fix BC break when dealing with entities (implementing an interface) on LiveProp [LiveComponent] Fix BC break when using PropertyTypeExtractorInterface::getType() on a #[LiveProp] property x when getter getX exists Jul 14, 2025
@Kocal Kocal marked this pull request as ready for review July 14, 2025 08:19
Comment on lines +38 to +40
if (method_exists($this->propertyTypeExtractor, 'getType') && !$this->typeResolver) {
throw new \LogicException('Symfony TypeInfo is required to use LiveProps. Try running "composer require symfony/type-info".');
}
Copy link
Member Author

@Kocal Kocal Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not happens since PropertyTypeInfoExtractor::getType() exists since 7.1, which also ship TypeInfo, but just in case..

Comment on lines 122 to 135
$reflectionType = $property->getType();
if ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) {
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()));
}

if ($type instanceof UnionType && !$type instanceof NullableType || $type instanceof IntersectionType) {
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".', $propertyName, $className));
$infoType = $this->propertyTypeExtractor->getType($className, $property->getName());

$collectionValueType = $infoType instanceof CollectionType ? $infoType->getCollectionValueType() : null;

if (null !== $collectionValueType && null !== $infoType) {
$type = $infoType;
} elseif (null !== $reflectionType) {
$type = $this->typeResolver->resolve($reflectionType);
} else {
$type = Type::mixed();
}
Copy link
Member Author

@Kocal Kocal Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure about this code, it feels fragile.

I tried to re-use the legacy logic and use ReflectionType or the result of PropertyTypeExtractorInterface::getType(), and now all tests pass, even the one added in this PR (which was previously breaking)

@Kocal Kocal requested review from kbond, mtarld and smnandre July 14, 2025 08:26
@Kocal Kocal force-pushed the 2888-live-hydrate-interface branch from 9cd665f to 30dca26 Compare July 14, 2025 19:43
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Jul 15, 2025
@Kocal Kocal force-pushed the 2888-live-hydrate-interface branch 2 times, most recently from ccadf9a to 33e1c4c Compare July 15, 2025 20:14
@Kocal
Copy link
Member Author

Kocal commented Jul 15, 2025

I've added some comments to improve comprehensibility.

Since the PR fixed the issue from issue's author (#2888 (comment)), and no tests are failing (either when using PropertyTypeExtractorInterface::getTypes() or PropertyTypeExtractorInterface::getType()), I'm merging.

Thanks both of you :)

…ce::getType()` on a `#[LiveProp]` property `x` when getter `getX` exists
@Kocal Kocal force-pushed the 2888-live-hydrate-interface branch from 33e1c4c to de302ef Compare July 15, 2025 20:37
@Kocal Kocal merged commit 42f5180 into symfony:2.x Jul 15, 2025
1 check was pending
@Kocal Kocal deleted the 2888-live-hydrate-interface branch July 24, 2025 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Bug Fix LiveComponent Status: Reviewed Has been reviewed by a maintainer

4 participants