Skip to content

Commit b12ee10

Browse files
committed
[LiveComponent] Add compatibility layer for Type::accepts() too (only available in TypeInfo 7.3)
1 parent 5a24c82 commit b12ee10

File tree

5 files changed

+113
-4
lines changed

5 files changed

+113
-4
lines changed

.github/workflows/.utils.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ install_property_info_for_version() {
2525
local min_stability="$2"
2626

2727
if [ "$php_version" = "8.2" ]; then
28-
composer require symfony/property-info:7.1.* symfony/type-info:7.3.*
28+
composer require symfony/property-info:7.1.* symfony/type-info:7.2.*
2929
elif [ "$php_version" = "8.3" ]; then
30-
composer require symfony/property-info:7.2.* symfony/type-info:7.3.*
30+
composer require symfony/property-info:7.2.* symfony/type-info:7.2.*
3131
elif [ "$php_version" = "8.4" ] && [ "$min_stability" = "stable" ]; then
3232
composer require symfony/property-info:7.3.* symfony/type-info:7.3.*
3333
elif [ "$php_version" = "8.4" ] && [ "$min_stability" = "dev" ]; then

src/LiveComponent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
},
5858
"conflict": {
5959
"symfony/config": "<5.4.0",
60+
"symfony/type-info": "<7.1",
6061
"symfony/property-info": "~7.0.0"
6162
},
6263
"config": {

src/LiveComponent/src/LiveComponentHydrator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ private function dehydrateValue(mixed $value, LivePropMetadata|LegacyLivePropMet
585585
foreach ($collectionValueType ? TypeHelper::traverse($collectionValueType) : [] as $t) {
586586
if ($t instanceof ObjectType) {
587587
foreach ($value as $key => $objectItem) {
588-
if (!$t->accepts($objectItem)) {
588+
if (!TypeHelper::accepts($t, $objectItem)) {
589589
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 one key had a different value of %s', $propMetadata->getName(), $parentObject::class, $t->getClassName(), get_debug_type($objectItem)));
590590
}
591591

src/LiveComponent/src/Util/QueryStringPropsExtractor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private function isValueTypeConsistent(mixed $value, LivePropMetadata|LegacyLive
9797
} else {
9898
$type = $livePropMetadata->getType();
9999

100-
return null === $type || $type->accepts($value);
100+
return null === $type || TypeHelper::accepts($type, $value);
101101
}
102102
}
103103
}

src/LiveComponent/src/Util/TypeHelper.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\TypeInfo\Type;
1515
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
1616
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
17+
use Symfony\Component\TypeInfo\TypeIdentifier;
1718

1819
/**
1920
* @author Hugo Alliaume <hugo@alliau.me>
@@ -22,6 +23,113 @@
2223
*/
2324
final class TypeHelper
2425
{
26+
/**
27+
* TODO: To remove when supporting symfony/type-info >=7.3 only.
28+
*/
29+
public static function accepts(Type $type, mixed $value): bool
30+
{
31+
$parentAccepts = function (Type $_type0, mixed $_value0): bool {
32+
$specification = static function (Type $_type1) use (&$specification, $_value0): bool {
33+
if ($_type1 instanceof WrappingTypeInterface) {
34+
return $_type1->wrappedTypeIsSatisfiedBy($specification);
35+
}
36+
37+
if ($_type1 instanceof CompositeTypeInterface) {
38+
return $_type1->composedTypesAreSatisfiedBy($specification);
39+
}
40+
41+
return TypeHelper::accepts($_type1, $_value0);
42+
};
43+
44+
return $_type0->isSatisfiedBy($specification);
45+
};
46+
47+
if ($type instanceof Type\ArrayShapeType) {
48+
if (!\is_array($value)) {
49+
return false;
50+
}
51+
52+
foreach ($type->getShape() as $key => $shapeValue) {
53+
if (!($shapeValue['optional'] ?? false) && !\array_key_exists($key, $value)) {
54+
return false;
55+
}
56+
}
57+
58+
foreach ($value as $key => $itemValue) {
59+
$valueType = $type->getShape()[$key]['type'] ?? false;
60+
61+
if ($valueType && !TypeHelper::accepts($valueType, $itemValue)) {
62+
return false;
63+
}
64+
65+
if (!$valueType && ($type->isSealed() || !TypeHelper::accepts($type->getExtraKeyType(), $key) || !TypeHelper::accepts($type->getExtraValueType(), $itemValue))) {
66+
return false;
67+
}
68+
}
69+
70+
return true;
71+
}
72+
73+
// Also supports EnumType and BackedEnumType
74+
if ($type instanceof Type\ObjectType) {
75+
$className = $type->getClassName();
76+
return $value instanceof $className;
77+
}
78+
79+
if ($type instanceof Type\BuiltinType) {
80+
return match ($type->getTypeIdentifier()) {
81+
TypeIdentifier::ARRAY => \is_array($value),
82+
TypeIdentifier::BOOL => \is_bool($value),
83+
TypeIdentifier::CALLABLE => \is_callable($value),
84+
TypeIdentifier::FALSE => false === $value,
85+
TypeIdentifier::FLOAT => \is_float($value),
86+
TypeIdentifier::INT => \is_int($value),
87+
TypeIdentifier::ITERABLE => is_iterable($value),
88+
TypeIdentifier::MIXED => true,
89+
TypeIdentifier::NULL => null === $value,
90+
TypeIdentifier::OBJECT => \is_object($value),
91+
TypeIdentifier::RESOURCE => \is_resource($value),
92+
TypeIdentifier::STRING => \is_string($value),
93+
TypeIdentifier::TRUE => true === $value,
94+
default => false,
95+
};
96+
}
97+
98+
if ($type instanceof Type\CollectionType) {
99+
if (!$parentAccepts($type, $value)) {
100+
return false;
101+
}
102+
103+
if ($type->isList() && (!\is_array($value) || !array_is_list($value))) {
104+
return false;
105+
}
106+
107+
$keyType = $type->getCollectionKeyType();
108+
$valueType = $type->getCollectionValueType();
109+
110+
if (is_iterable($value)) {
111+
foreach ($value as $k => $v) {
112+
// key or value do not match
113+
if (!TypeHelper::accepts($keyType, $k) || !TypeHelper::accepts($valueType, $v)) {
114+
return false;
115+
}
116+
}
117+
}
118+
119+
return true;
120+
}
121+
122+
if ($type instanceof Type\NullableType) {
123+
return null === $value || $parentAccepts($type, $value);
124+
}
125+
126+
if ($type instanceof Type\GenericType || $type instanceof Type\TemplateType || $type instanceof Type\UnionType || $type instanceof Type\IntersectionType) {
127+
return $parentAccepts($type, $value);
128+
}
129+
130+
return false;
131+
}
132+
25133
/**
26134
* TODO: To remove when supporting symfony/type-info >=7.3 only.
27135
*

0 commit comments

Comments
 (0)