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 <[email protected]>
 *
 * 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, smnandre and mtarld 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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BC break in LiveComponentHydrator
4 participants