From 49fa189bb58db122985110c1308ce1cd835e64f7 Mon Sep 17 00:00:00 2001 From: Kevin Sliedrecht Date: Tue, 28 Oct 2025 09:34:43 +0100 Subject: [PATCH 1/5] Fix liveProp union type --- src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php index 916963f924f..9b9d024daf4 100644 --- a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php +++ b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php @@ -84,7 +84,7 @@ public function createLivePropMetadata(string $className, string $propertyName, { $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())); + return new LivePropMetadata($property->getName(), $liveProp, Type::mixed()); } // BC layer when "symfony/type-info" is not available From 1c92999c730b5f91f1a1750e4063e120e1815809 Mon Sep 17 00:00:00 2001 From: Kevin Sliedrecht Date: Wed, 29 Oct 2025 09:13:53 +0100 Subject: [PATCH 2/5] Update changelog --- src/LiveComponent/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md index 50ba92558b3..0da7e033c53 100644 --- a/src/LiveComponent/CHANGELOG.md +++ b/src/LiveComponent/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## 2.32 + +- Add support for `LiveProp` union types in Live Components. + `LiveComponentMetadataFactory` can now create `liveProp` metadata for union types. + Note: This feature only works when using a custom `hydrate` and `dehydrate` implementation. + ## 2.31 - Add browser events assertions in `InteractsWithLiveComponents`: From a40abae12abe77b56b2652cc483889824f768f70 Mon Sep 17 00:00:00 2001 From: Kevin Sliedrecht Date: Wed, 29 Oct 2025 09:14:34 +0100 Subject: [PATCH 3/5] Add LiveComponentMetadataFactory union type test --- .../Fixtures/Component/WithUnionType.php | 25 +++++++++++++++++++ .../LiveComponentMetadataFactoryTest.php | 18 +++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/LiveComponent/tests/Fixtures/Component/WithUnionType.php diff --git a/src/LiveComponent/tests/Fixtures/Component/WithUnionType.php b/src/LiveComponent/tests/Fixtures/Component/WithUnionType.php new file mode 100644 index 00000000000..389afd44398 --- /dev/null +++ b/src/LiveComponent/tests/Fixtures/Component/WithUnionType.php @@ -0,0 +1,25 @@ + + * + * 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\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\Attribute\LiveProp; +use Symfony\UX\LiveComponent\DefaultActionTrait; + +#[AsLiveComponent('with_union_type')] +final class WithUnionType +{ + use DefaultActionTrait; + + #[LiveProp] + public int|float|null $unionProp = null; +} diff --git a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php index 3529804443d..c517618e75d 100644 --- a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php +++ b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php @@ -12,9 +12,11 @@ namespace Symfony\UX\LiveComponent\Tests\Functional\Metadata; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\TypeInfo\Type; use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory; use Symfony\UX\LiveComponent\Metadata\UrlMapping; use Symfony\UX\LiveComponent\Tests\Fixtures\Component\ComponentWithUrlBoundProps; +use Symfony\UX\LiveComponent\Tests\Fixtures\Component\WithUnionType; class LiveComponentMetadataFactoryTest extends KernelTestCase { @@ -42,4 +44,20 @@ public function testQueryStringMapping() $this->assertEquals(new UrlMapping(as: 'q'), $propsMetadataByName['boundPropWithAlias']->urlMapping()); $this->assertNotNull($propsMetadataByName['boundPropWithCustomAlias']); } + + public function testLivePropUnionType() + { + /** @var LiveComponentMetadataFactory $metadataFactory */ + $metadataFactory = self::getContainer()->get('ux.live_component.metadata_factory'); + + $class = new \ReflectionClass(WithUnionType::class); + $propsMetadata = $metadataFactory->createPropMetadatas($class); + + $propsMetadataByName = []; + foreach ($propsMetadata as $propMetadata) { + $propsMetadataByName[$propMetadata->getName()] = $propMetadata; + } + + $this->assertEquals(Type::mixed(), $propsMetadataByName['unionProp']->getType()); + } } From d85f2927c2d9d98edf6b06388453b9de5fdc8c9e Mon Sep 17 00:00:00 2001 From: Kevin Sliedrecht Date: Wed, 29 Oct 2025 09:42:18 +0100 Subject: [PATCH 4/5] Fix LiveComponentMetadataFactoryTest testLivePropUnionType --- .../src/Metadata/LiveComponentMetadataFactory.php | 11 ++++++++--- ...ithUnionType.php => WithUnionIntersectionType.php} | 9 +++++++-- .../Metadata/LiveComponentMetadataFactoryTest.php | 8 ++++---- 3 files changed, 19 insertions(+), 9 deletions(-) rename src/LiveComponent/tests/Fixtures/Component/{WithUnionType.php => WithUnionIntersectionType.php} (66%) diff --git a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php index 9b9d024daf4..6d111dcae34 100644 --- a/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php +++ b/src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php @@ -83,12 +83,13 @@ public function createPropMetadatas(\ReflectionClass $class): array public function createLivePropMetadata(string $className, string $propertyName, \ReflectionProperty $property, LiveProp $liveProp): LivePropMetadata|LegacyLivePropMetadata { $reflectionType = $property->getType(); - if ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) { - return new LivePropMetadata($property->getName(), $liveProp, Type::mixed()); - } // BC layer when "symfony/type-info" is not available if (!method_exists($this->propertyTypeExtractor, 'getType')) { + if ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) { + return new LegacyLivePropMetadata($property->getName(), $liveProp, 'mixed', true, $reflectionType->allowsNull(), null); + } + $infoTypes = $this->propertyTypeExtractor->getTypes($className, $propertyName) ?? []; $collectionValueType = null; @@ -122,6 +123,10 @@ public function createLivePropMetadata(string $className, string $propertyName, $collectionValueType ); } else { + if ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) { + return new LivePropMetadata($property->getName(), $liveProp, Type::mixed()); + } + $infoType = $this->propertyTypeExtractor->getType($className, $property->getName()); if ($infoType instanceof CollectionType) { diff --git a/src/LiveComponent/tests/Fixtures/Component/WithUnionType.php b/src/LiveComponent/tests/Fixtures/Component/WithUnionIntersectionType.php similarity index 66% rename from src/LiveComponent/tests/Fixtures/Component/WithUnionType.php rename to src/LiveComponent/tests/Fixtures/Component/WithUnionIntersectionType.php index 389afd44398..e2377c51f38 100644 --- a/src/LiveComponent/tests/Fixtures/Component/WithUnionType.php +++ b/src/LiveComponent/tests/Fixtures/Component/WithUnionIntersectionType.php @@ -14,12 +14,17 @@ use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\DefaultActionTrait; +use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\IntEnum; +use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\ZeroIntEnum; -#[AsLiveComponent('with_union_type')] -final class WithUnionType +#[AsLiveComponent('with_union_intersection_type')] +final class WithUnionIntersectionType { use DefaultActionTrait; #[LiveProp] public int|float|null $unionProp = null; + + #[LiveProp] + public IntEnum&ZeroIntEnum $intersectionProp; } diff --git a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php index c517618e75d..0855076a562 100644 --- a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php +++ b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php @@ -12,11 +12,10 @@ namespace Symfony\UX\LiveComponent\Tests\Functional\Metadata; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\TypeInfo\Type; use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory; use Symfony\UX\LiveComponent\Metadata\UrlMapping; use Symfony\UX\LiveComponent\Tests\Fixtures\Component\ComponentWithUrlBoundProps; -use Symfony\UX\LiveComponent\Tests\Fixtures\Component\WithUnionType; +use Symfony\UX\LiveComponent\Tests\Fixtures\Component\WithUnionIntersectionType; class LiveComponentMetadataFactoryTest extends KernelTestCase { @@ -50,7 +49,7 @@ public function testLivePropUnionType() /** @var LiveComponentMetadataFactory $metadataFactory */ $metadataFactory = self::getContainer()->get('ux.live_component.metadata_factory'); - $class = new \ReflectionClass(WithUnionType::class); + $class = new \ReflectionClass(WithUnionIntersectionType::class); $propsMetadata = $metadataFactory->createPropMetadatas($class); $propsMetadataByName = []; @@ -58,6 +57,7 @@ public function testLivePropUnionType() $propsMetadataByName[$propMetadata->getName()] = $propMetadata; } - $this->assertEquals(Type::mixed(), $propsMetadataByName['unionProp']->getType()); + $this->assertEquals('mixed', (string)$propsMetadataByName['unionProp']->getType()); + $this->assertEquals('mixed', (string)$propsMetadataByName['intersectionProp']->getType()); } } From 2da2c11df5af0794f2afee376111c257442ab006 Mon Sep 17 00:00:00 2001 From: Kevin Sliedrecht Date: Wed, 29 Oct 2025 09:45:55 +0100 Subject: [PATCH 5/5] Fix php-cs-fixer --- .../Functional/Metadata/LiveComponentMetadataFactoryTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php index 0855076a562..8becdf4bb03 100644 --- a/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php +++ b/src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php @@ -57,7 +57,7 @@ public function testLivePropUnionType() $propsMetadataByName[$propMetadata->getName()] = $propMetadata; } - $this->assertEquals('mixed', (string)$propsMetadataByName['unionProp']->getType()); - $this->assertEquals('mixed', (string)$propsMetadataByName['intersectionProp']->getType()); + $this->assertEquals('mixed', (string) $propsMetadataByName['unionProp']->getType()); + $this->assertEquals('mixed', (string) $propsMetadataByName['intersectionProp']->getType()); } }