Skip to content

Commit 1f97e19

Browse files
committed
feature #2607 [LiveComponent] Fix PropertyTypeExtractorInterface::getTypes() deprecation, use TypeInfo ^7.2 Type (mtarld)
This PR was merged into the 2.x branch. Discussion ---------- [LiveComponent] Fix `PropertyTypeExtractorInterface::getTypes()` deprecation, use TypeInfo ^7.2 `Type` | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Issues | Fix #1708, fix #2778 | License | MIT Fix the `PropertyTypeExtractorInterface::getTypes()` deprecation and use `PropertyTypeExtractorInterface::getType()` (based on TypeInfo `Type`) instead. This PR allows TypeInfo ^7.2 to be used, thanks to the compatibility layers added for `Type::accepts()` and `Type::traverse()` (added in 7.3). In order to reduce frictions when users will upgrade their apps dependencies. The CI changed a bit too: - When testing LiveComponent, with PHP 8.2, we install `symfony/property-info:7.1.* symfony/type-info:7.2.*` - When testing LiveComponent, with PHP 8.3, we install `symfony/property-info:7.2.* symfony/type-info:7.2.*` - When testing LiveComponent, with PHP 8.4, we install `symfony/property-info:7.3.* symfony/type-info:7.3.*` - When testing LiveComponent, with PHP 8.4 (and dev deps), we install `symfony/property-info:>=7.3 symfony/type-info:>=7.3` Allowing us to covers a maximum versions of PropertyInfo and TypeInfo. Commits ------- 7a08cce [LiveComponent] Fix `PropertyTypeExtractorInterface::getTypes()`` deprecation, use TypeInfo ^7.2 `Type`
2 parents 94f27c1 + 7a08cce commit 1f97e19

13 files changed

+646
-134
lines changed

.github/workflows/.utils.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,19 @@ _run_task() {
1919
exit $ok
2020
}
2121
export -f _run_task
22+
23+
install_property_info_for_version() {
24+
local php_version="$1"
25+
local min_stability="$2"
26+
27+
if [ "$php_version" = "8.2" ]; then
28+
composer require symfony/property-info:7.1.* symfony/type-info:7.2.*
29+
elif [ "$php_version" = "8.3" ]; then
30+
composer require symfony/property-info:7.2.* symfony/type-info:7.2.*
31+
elif [ "$php_version" = "8.4" ] && [ "$min_stability" = "stable" ]; then
32+
composer require symfony/property-info:7.3.* symfony/type-info:7.3.*
33+
elif [ "$php_version" = "8.4" ] && [ "$min_stability" = "dev" ]; then
34+
composer require symfony/property-info:>=7.3 symfony/type-info:>=7.3
35+
fi
36+
}
37+
export -f install_property_info_for_version

.github/workflows/unit-tests.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ jobs:
7171
run: |
7272
source .github/workflows/.utils.sh
7373
74-
echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_MIN_STAB && $COMPOSER_UP && $PHPUNIT)'"
74+
echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} \
75+
'(cd src/{} \
76+
&& $COMPOSER_MIN_STAB \
77+
&& $COMPOSER_UP \
78+
&& if [ {} = LiveComponent ]; then install_property_info_for_version \"${{ matrix.php-version }}\" \"${{ matrix.minimum-stability }}\"; fi \
79+
&& $PHPUNIT)'"
7580
7681
js:
7782
runs-on: ubuntu-latest

src/LiveComponent/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## 2.26.0
44

55
- `LiveProp`: Pass the property name as second parameter of the `modifier` callable
6+
- Add compatibility layer to fix deprecation with `Symfony\Component\PropertyInfo\PropertyInfoExtractor::getTypes()`.
7+
If you use PHP 8.2 or higher, we recommend you to update dependency `symfony/property-info` to at least 7.1.0
68

79
## 2.25.0
810

src/LiveComponent/composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@
5656
"zenstruck/foundry": "^2.0"
5757
},
5858
"conflict": {
59-
"symfony/config": "<5.4.0"
59+
"symfony/config": "<5.4.0",
60+
"symfony/type-info": "<7.1",
61+
"symfony/property-info": "~7.0.0"
6062
},
6163
"config": {
6264
"sort-packages": true

src/LiveComponent/doc/index.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,17 @@ library. Make sure it is installed in you application:
484484
485485
$ composer require phpdocumentor/reflection-docblock
486486
487+
.. versionadded:: 2.26
488+
489+
Support for `Symfony TypeInfo`_ component was added in LiveComponents 2.26.
490+
491+
To get rid of deprecations about ``PropertyInfoExtractor::getTypes()`` from the `Symfony PropertyInfo`_ component,
492+
ensure to upgrade ``symfony/property-info`` to at least 7.1, which requires **PHP 8.2**::
493+
494+
.. code-block:: terminal
495+
496+
$ composer require symfony/property-info:^7.1
497+
487498
Writable Object Properties or Array Keys
488499
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
489500

@@ -3900,3 +3911,5 @@ promise. However, any internal implementation in the JavaScript files
39003911
.. _`setting the locale in the request`: https://symfony.com/doc/current/translation.html#translation-locale
39013912
.. _`Stimulus action parameter`: https://stimulus.hotwired.dev/reference/actions#action-parameters
39023913
.. _`@symfony/ux-live-component npm package`: https://www.npmjs.com/package/@symfony/ux-live-component
3914+
.. _`Symfony TypeInfo`: https://symfony.com/doc/current/components/type_info.html
3915+
.. _`Symfony PropertyInfo`: https://symfony.com/doc/current/components/property_info.html

src/LiveComponent/src/LiveComponentHydrator.php

Lines changed: 175 additions & 49 deletions
Large diffs are not rendered by default.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Metadata;
13+
14+
use Symfony\Component\PropertyInfo\Type;
15+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
16+
17+
/**
18+
* @author Kevin Bond <[email protected]>
19+
*
20+
* @internal
21+
*/
22+
final class LegacyLivePropMetadata
23+
{
24+
public function __construct(
25+
private string $name,
26+
private LiveProp $liveProp,
27+
private ?string $typeName,
28+
private bool $isBuiltIn,
29+
private bool $allowsNull,
30+
private ?Type $collectionValueType,
31+
) {
32+
}
33+
34+
public function getName(): string
35+
{
36+
return $this->name;
37+
}
38+
39+
public function getType(): ?string
40+
{
41+
return $this->typeName;
42+
}
43+
44+
public function isBuiltIn(): bool
45+
{
46+
return $this->isBuiltIn;
47+
}
48+
49+
public function allowsNull(): bool
50+
{
51+
return $this->allowsNull;
52+
}
53+
54+
public function urlMapping(): ?UrlMapping
55+
{
56+
return $this->liveProp->url() ?: null;
57+
}
58+
59+
public function calculateFieldName(object $component, string $fallback): string
60+
{
61+
return $this->liveProp->calculateFieldName($component, $fallback);
62+
}
63+
64+
/**
65+
* @return array<string>
66+
*/
67+
public function writablePaths(): array
68+
{
69+
return $this->liveProp->writablePaths();
70+
}
71+
72+
public function hydrateMethod(): ?string
73+
{
74+
return $this->liveProp->hydrateMethod();
75+
}
76+
77+
public function dehydrateMethod(): ?string
78+
{
79+
return $this->liveProp->dehydrateMethod();
80+
}
81+
82+
public function isIdentityWritable(): bool
83+
{
84+
return $this->liveProp->isIdentityWritable();
85+
}
86+
87+
public function acceptUpdatesFromParent(): bool
88+
{
89+
return $this->liveProp->acceptUpdatesFromParent();
90+
}
91+
92+
public function useSerializerForHydration(): bool
93+
{
94+
return $this->liveProp->useSerializerForHydration();
95+
}
96+
97+
public function serializationContext(): array
98+
{
99+
return $this->liveProp->serializationContext();
100+
}
101+
102+
public function collectionValueType(): ?Type
103+
{
104+
return $this->collectionValueType;
105+
}
106+
107+
public function getFormat(): ?string
108+
{
109+
return $this->liveProp->format();
110+
}
111+
112+
public function onUpdated(): string|array|null
113+
{
114+
return $this->liveProp->onUpdated();
115+
}
116+
117+
public function hasModifier(): bool
118+
{
119+
return null !== $this->liveProp->modifier();
120+
}
121+
122+
/**
123+
* Applies a modifier specified in LiveProp attribute.
124+
*
125+
* If a modifier is specified, a modified clone is returned.
126+
* Otherwise, the metadata is returned as it is.
127+
*/
128+
public function withModifier(object $component): self
129+
{
130+
if (null === ($modifier = $this->liveProp->modifier())) {
131+
return $this;
132+
}
133+
134+
if (!method_exists($component, $modifier)) {
135+
throw new \LogicException(\sprintf('Method "%s::%s()" given in LiveProp "modifier" does not exist.', $component::class, $modifier));
136+
}
137+
138+
$modifiedLiveProp = $component->{$modifier}($this->liveProp, $this->getName());
139+
if (!$modifiedLiveProp instanceof LiveProp) {
140+
throw new \LogicException(\sprintf('Method "%s::%s()" should return an instance of "%s" (given: "%s").', $component::class, $modifier, LiveProp::class, get_debug_type($modifiedLiveProp)));
141+
}
142+
143+
$clone = clone $this;
144+
$clone->liveProp = $modifiedLiveProp;
145+
146+
return $clone;
147+
}
148+
}

src/LiveComponent/src/Metadata/LiveComponentMetadata.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ class LiveComponentMetadata
2222
{
2323
public function __construct(
2424
private ComponentMetadata $componentMetadata,
25-
/** @var LivePropMetadata[] */
25+
/** @var list<LivePropMetadata|LegacyLivePropMetadata> */
2626
private array $livePropsMetadata,
2727
) {
2828
uasort(
2929
$this->livePropsMetadata,
30-
static fn (LivePropMetadata $a, LivePropMetadata $b) => $a->hasModifier() <=> $b->hasModifier()
30+
static fn (LivePropMetadata|LegacyLivePropMetadata $a, LivePropMetadata|LegacyLivePropMetadata $b) => $a->hasModifier() <=> $b->hasModifier()
3131
);
3232
}
3333

@@ -37,7 +37,7 @@ public function getComponentMetadata(): ComponentMetadata
3737
}
3838

3939
/**
40-
* @return LivePropMetadata[]
40+
* @return list<LivePropMetadata|LegacyLivePropMetadata>
4141
*/
4242
public function getAllLivePropsMetadata(object $component): iterable
4343
{
@@ -55,7 +55,7 @@ public function getAllLivePropsMetadata(object $component): iterable
5555
*/
5656
public function getOnlyPropsThatAcceptUpdatesFromParent(array $inputProps): array
5757
{
58-
$writableProps = array_filter($this->livePropsMetadata, function (LivePropMetadata $livePropMetadata) {
58+
$writableProps = array_filter($this->livePropsMetadata, function (LivePropMetadata|LegacyLivePropMetadata $livePropMetadata) {
5959
return $livePropMetadata->acceptUpdatesFromParent();
6060
});
6161

src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
namespace Symfony\UX\LiveComponent\Metadata;
1313

1414
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
15-
use Symfony\Component\PropertyInfo\Type;
15+
use Symfony\Component\PropertyInfo\Type as LegacyType;
16+
use Symfony\Component\TypeInfo\Type\IntersectionType;
17+
use Symfony\Component\TypeInfo\Type\NullableType;
18+
use Symfony\Component\TypeInfo\Type\UnionType;
1619
use Symfony\Contracts\Service\ResetInterface;
1720
use Symfony\UX\LiveComponent\Attribute\LiveProp;
1821
use Symfony\UX\TwigComponent\ComponentFactory;
@@ -48,7 +51,7 @@ public function getMetadata(string $name): LiveComponentMetadata
4851
}
4952

5053
/**
51-
* @return LivePropMetadata[]
54+
* @return list<LivePropMetadata|LegacyLivePropMetadata>
5255
*
5356
* @internal
5457
*/
@@ -72,43 +75,54 @@ public function createPropMetadatas(\ReflectionClass $class): array
7275
return array_values($metadatas);
7376
}
7477

75-
public function createLivePropMetadata(string $className, string $propertyName, \ReflectionProperty $property, LiveProp $liveProp): LivePropMetadata
78+
public function createLivePropMetadata(string $className, string $propertyName, \ReflectionProperty $property, LiveProp $liveProp): LivePropMetadata|LegacyLivePropMetadata
7679
{
77-
$type = $property->getType();
78-
if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
79-
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()));
80-
}
80+
// BC layer when "symfony/type-info" is not available
81+
if (!method_exists($this->propertyTypeExtractor, 'getType')) {
82+
$type = $property->getType();
83+
if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
84+
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()));
85+
}
8186

82-
$infoTypes = $this->propertyTypeExtractor->getTypes($className, $propertyName) ?? [];
87+
$infoTypes = $this->propertyTypeExtractor->getTypes($className, $propertyName) ?? [];
8388

84-
$collectionValueType = null;
85-
foreach ($infoTypes as $infoType) {
86-
if ($infoType->isCollection()) {
87-
foreach ($infoType->getCollectionValueTypes() as $valueType) {
88-
$collectionValueType = $valueType;
89-
break;
89+
$collectionValueType = null;
90+
foreach ($infoTypes as $infoType) {
91+
if ($infoType->isCollection()) {
92+
foreach ($infoType->getCollectionValueTypes() as $valueType) {
93+
$collectionValueType = $valueType;
94+
break;
95+
}
9096
}
9197
}
92-
}
9398

94-
if (null === $type && null === $collectionValueType && isset($infoTypes[0])) {
95-
$infoType = Type::BUILTIN_TYPE_OBJECT === $infoTypes[0]->getBuiltinType() ? $infoTypes[0]->getClassName() : $infoTypes[0]->getBuiltinType();
96-
$isTypeBuiltIn = null === $infoTypes[0]->getClassName();
97-
$isTypeNullable = $infoTypes[0]->isNullable();
99+
if (null === $type && null === $collectionValueType && isset($infoTypes[0])) {
100+
$infoType = LegacyType::BUILTIN_TYPE_OBJECT === $infoTypes[0]->getBuiltinType() ? $infoTypes[0]->getClassName() : $infoTypes[0]->getBuiltinType();
101+
$isTypeBuiltIn = null === $infoTypes[0]->getClassName();
102+
$isTypeNullable = $infoTypes[0]->isNullable();
103+
} else {
104+
$infoType = $type?->getName();
105+
$isTypeBuiltIn = $type?->isBuiltin() ?? false;
106+
$isTypeNullable = $type?->allowsNull() ?? true;
107+
}
108+
109+
return new LegacyLivePropMetadata(
110+
$property->getName(),
111+
$liveProp,
112+
$infoType,
113+
$isTypeBuiltIn,
114+
$isTypeNullable,
115+
$collectionValueType
116+
);
98117
} else {
99-
$infoType = $type?->getName();
100-
$isTypeBuiltIn = $type?->isBuiltin() ?? false;
101-
$isTypeNullable = $type?->allowsNull() ?? true;
102-
}
118+
$type = $this->propertyTypeExtractor->getType($className, $property->getName());
119+
120+
if ($type instanceof UnionType && !$type instanceof NullableType || $type instanceof IntersectionType) {
121+
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));
122+
}
103123

104-
return new LivePropMetadata(
105-
$property->getName(),
106-
$liveProp,
107-
$infoType,
108-
$isTypeBuiltIn,
109-
$isTypeNullable,
110-
$collectionValueType
111-
);
124+
return new LivePropMetadata($property->getName(), $liveProp, $type);
125+
}
112126
}
113127

114128
/**

0 commit comments

Comments
 (0)