Skip to content

Commit ffd86b8

Browse files
committed
feat(metadata): use TypeInfo's Type
1 parent ad54075 commit ffd86b8

23 files changed

+384
-135
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@
111111
"symfony/http-foundation": "^6.4 || ^7.0",
112112
"symfony/http-kernel": "^6.4 || ^7.0",
113113
"symfony/property-access": "^6.4 || ^7.0",
114-
"symfony/property-info": "^6.4 || ^7.0",
114+
"symfony/property-info": "^7.1",
115115
"symfony/serializer": "^6.4 || ^7.0",
116116
"symfony/translation-contracts": "^3.3",
117+
"symfony/type-info": "^7.2",
117118
"symfony/web-link": "^6.4 || ^7.0",
118119
"willdurand/negotiation": "^3.1"
119120
},

src/Metadata/ApiProperty.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16-
use Symfony\Component\PropertyInfo\Type;
16+
use ApiPlatform\Metadata\Util\PropertyInfoToTypeInfoHelper;
17+
use Symfony\Component\PropertyInfo\Type as LegacyType;
1718
use Symfony\Component\Serializer\Attribute\Context;
1819
use Symfony\Component\Serializer\Attribute\Groups;
1920
use Symfony\Component\Serializer\Attribute\Ignore;
2021
use Symfony\Component\Serializer\Attribute\MaxDepth;
2122
use Symfony\Component\Serializer\Attribute\SerializedName;
2223
use Symfony\Component\Serializer\Attribute\SerializedPath;
24+
use Symfony\Component\TypeInfo\Type;
2325

2426
/**
2527
* ApiProperty annotation.
@@ -31,6 +33,15 @@ final class ApiProperty
3133
{
3234
private ?array $types;
3335
private ?array $serialize;
36+
private ?Type $phpType;
37+
38+
/**
39+
* Used to know if only legacy types are defined without triggering deprecation.
40+
* To be removed in 5.0.
41+
*
42+
* @internal
43+
*/
44+
public bool $usesLegacyType = false;
3445

3546
/**
3647
* @param bool|null $readableLink https://api-platform.com/docs/core/serialization/#force-iri-with-relations-of-the-same-type-parentchilds-relations
@@ -47,10 +58,11 @@ final class ApiProperty
4758
* @param string|\Stringable|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization
4859
* @param string[] $types the RDF types of this property
4960
* @param string[] $iris
50-
* @param Type[] $builtinTypes
61+
* @param LegacyType[] $builtinTypes
5162
* @param string|null $uriTemplate (experimental) whether to return the subRessource collection IRI instead of an iterable of IRI
5263
* @param string|null $property The property name
5364
* @param Context|Groups|Ignore|SerializedName|SerializedPath|MaxDepth|array<array-key, Context|Groups|Ignore|SerializedName|SerializedPath|MaxDepth> $serialize Serializer attributes
65+
* @param Type $phpType The internal PHP type
5466
*/
5567
public function __construct(
5668
private ?string $description = null,
@@ -206,6 +218,8 @@ public function __construct(
206218
array|string|null $types = null,
207219
/*
208220
* The related php types.
221+
*
222+
* deprecated since 4.1, use "phpType" instead.
209223
*/
210224
private ?array $builtinTypes = null,
211225
private ?array $schema = null,
@@ -221,9 +235,23 @@ public function __construct(
221235
*/
222236
private ?bool $hydra = null,
223237
private array $extraProperties = [],
238+
?Type $phpType = null,
224239
) {
225240
$this->types = \is_string($types) ? (array) $types : $types;
226241
$this->serialize = \is_array($serialize) ? $serialize : [$serialize];
242+
$this->phpType = $phpType;
243+
244+
if ($this->builtinTypes) {
245+
// trigger_deprecation('api_platform/metadata', '4.1', 'The "builtinTypes" argument of "%s()" is deprecated, use "phpType" instead.');
246+
247+
$this->usesLegacyType = true;
248+
249+
if (!$this->phpType) {
250+
$this->phpType = PropertyInfoToTypeInfoHelper::convertLegacyTypesToType($this->builtinTypes);
251+
}
252+
} elseif ($this->phpType) {
253+
$this->builtinTypes = PropertyInfoToTypeInfoHelper::convertTypeToLegacyTypes($this->phpType);
254+
}
227255
}
228256

229257
public function getProperty(): ?string
@@ -490,20 +518,43 @@ public function withTypes(array|string $types = []): static
490518
}
491519

492520
/**
493-
* @return Type[]
521+
* deprecated since 4.1, use "getPhpType" instead.
522+
*
523+
* @return LegacyType[]
494524
*/
495525
public function getBuiltinTypes(): ?array
496526
{
527+
// trigger_deprecation('api-platform/metadata', '4.1', 'The "%s()" method is deprecated, use "%s::getPhpType()" instead.', __METHOD__, self::class);
528+
497529
return $this->builtinTypes;
498530
}
499531

500532
/**
501-
* @param Type[] $builtinTypes
533+
* deprecated since 4.1, use "withPhpType" instead.
534+
*
535+
* @param LegacyType[] $builtinTypes
502536
*/
503537
public function withBuiltinTypes(array $builtinTypes = []): static
504538
{
539+
// trigger_deprecation('api-platform/metadata', '4.1', 'The "%s()" method is deprecated, use "%s::withPhpType()" instead.', __METHOD__, self::class);
540+
505541
$self = clone $this;
506542
$self->builtinTypes = $builtinTypes;
543+
$self->phpType = PropertyInfoToTypeInfoHelper::convertLegacyTypesToType($builtinTypes);
544+
545+
return $self;
546+
}
547+
548+
public function getPhpType(): ?Type
549+
{
550+
return $this->phpType;
551+
}
552+
553+
public function withPhpType(?Type $phpType): self
554+
{
555+
$self = clone $this;
556+
$self->phpType = $phpType;
557+
$self->builtinTypes = PropertyInfoToTypeInfoHelper::convertTypeToLegacyTypes($phpType);
507558

508559
return $self;
509560
}

src/Metadata/Extractor/XmlPropertyExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ protected function extractPath(string $path): void
7474
'genId' => $this->phpize($property, 'genId', 'bool'),
7575
'uriTemplate' => $this->phpize($property, 'uriTemplate', 'string'),
7676
'property' => $this->phpize($property, 'property', 'string'),
77+
'phpType' => $this->phpize($property, 'phpType', 'string'),
7778
];
7879
}
7980
}

src/Metadata/Extractor/YamlPropertyExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ private function buildProperties(array $resourcesYaml): void
9595
'genId' => $this->phpize($propertyValues, 'genId', 'bool'),
9696
'uriTemplate' => $this->phpize($propertyValues, 'uriTemplate', 'string'),
9797
'property' => $this->phpize($propertyValues, 'property', 'string'),
98+
'phpType' => $this->phpize($propertyValues, 'phpType', 'string'),
9899
];
99100
}
100101
}

src/Metadata/Extractor/schema/properties.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<xsd:attribute name="property" type="xsd:string"/>
4848
<xsd:attribute name="uriTemplate" type="xsd:string"/>
4949
<xsd:attribute name="hydra" type="xsd:boolean"/>
50+
<xsd:attribute name="phpType" type="xsd:string"/>
5051
</xsd:complexType>
5152

5253
<xsd:complexType name="types">

src/Metadata/IdentifiersExtractor.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
2424
use Symfony\Component\PropertyAccess\PropertyAccess;
2525
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
26+
use Symfony\Component\TypeInfo\Type;
27+
use Symfony\Component\TypeInfo\Type\CollectionType;
28+
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
29+
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
2630

2731
/**
2832
* {@inheritdoc}
@@ -110,21 +114,25 @@ private function getIdentifierValue(object $item, string $class, string $propert
110114
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
111115
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
112116

113-
$types = $propertyMetadata->getBuiltinTypes();
114-
if (null === ($type = $types[0] ?? null)) {
117+
if (null === $type = $propertyMetadata->getPhpType()) {
115118
continue;
116119
}
117120

118-
try {
119-
if ($type->isCollection()) {
120-
$collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
121+
$collectionValueIsIdentifiedByClass = function (Type $type) use (&$collectionValueIsIdentifiedByClass, $class): bool {
122+
return match (true) {
123+
$type instanceof CollectionType => $type->getCollectionValueType()->isIdentifiedBy($class),
124+
$type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($collectionValueIsIdentifiedByClass),
125+
$type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($collectionValueIsIdentifiedByClass),
126+
default => false,
127+
};
128+
};
121129

122-
if (null !== $collectionValueType && $collectionValueType->getClassName() === $class) {
123-
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, \sprintf('%s[0].%s', $propertyName, $property)), $parameterName);
124-
}
130+
try {
131+
if ($type->isSatisfiedBy($collectionValueIsIdentifiedByClass)) {
132+
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, \sprintf('%s[0].%s', $propertyName, $property)), $parameterName);
125133
}
126134

127-
if ($type->getClassName() === $class) {
135+
if ($type->isIdentifiedBy($class)) {
128136
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, "$propertyName.$property"), $parameterName);
129137
}
130138
} catch (NoSuchPropertyException $e) {

src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,23 @@ private function createMetadata(ApiProperty $attribute, ?ApiProperty $propertyMe
121121
}
122122

123123
foreach (get_class_methods(ApiProperty::class) as $method) {
124-
if (preg_match('/^(?:get|is)(.*)/', (string) $method, $matches) && null !== $val = $attribute->{$method}()) {
125-
$propertyMetadata = $propertyMetadata->{"with{$matches[1]}"}($val);
124+
if (preg_match('/^(?:get|is)(.*)/', (string) $method, $matches)) {
125+
// BC layer, to remove in 5.0
126+
if ('getBuiltinTypes' === $method) {
127+
if (!$attribute->usesLegacyType) {
128+
continue;
129+
}
130+
131+
if ($builtinTypes = $attribute->getBuiltinTypes()) {
132+
$propertyMetadata = $propertyMetadata->withBuiltinTypes($builtinTypes);
133+
}
134+
135+
continue;
136+
}
137+
138+
if (null !== $val = $attribute->{$method}()) {
139+
$propertyMetadata = $propertyMetadata->{"with{$matches[1]}"}($val);
140+
}
126141
}
127142
}
128143

src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
use ApiPlatform\Metadata\ApiProperty;
1818
use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
1919
use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface;
20-
use Symfony\Component\PropertyInfo\Type;
20+
use Symfony\Component\PropertyInfo\Type as LegacyType;
21+
use Symfony\Component\TypeInfo\Type;
2122

2223
/**
2324
* Creates properties's metadata using an extractor.
@@ -60,7 +61,9 @@ public function create(string $resourceClass, string $property, array $options =
6061

6162
foreach ($propertyMetadata as $key => $value) {
6263
if ('builtinTypes' === $key && null !== $value) {
63-
$value = array_map(fn (string $builtinType): Type => new Type($builtinType), $value);
64+
$value = array_map(static fn (string $builtinType): LegacyType => new LegacyType($builtinType), $value);
65+
} elseif ('phpType' === $key && null !== $value) {
66+
$value = Type::builtin($value);
6467
}
6568

6669
$methodName = 'with'.ucfirst($key);

src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515

1616
use ApiPlatform\Metadata\ApiProperty;
1717
use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
18-
use Doctrine\Common\Collections\ArrayCollection;
1918
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
20-
use Symfony\Component\PropertyInfo\Type;
2119

2220
/**
2321
* PropertyInfo metadata loader decorator.
@@ -45,17 +43,8 @@ public function create(string $resourceClass, string $property, array $options =
4543
}
4644
}
4745

48-
if (!$propertyMetadata->getBuiltinTypes()) {
49-
$types = $this->propertyInfo->getTypes($resourceClass, $property, $options) ?? [];
50-
51-
foreach ($types as $i => $type) {
52-
// Temp fix for https://github.com/symfony/symfony/pull/52699
53-
if (ArrayCollection::class === $type->getClassName()) {
54-
$types[$i] = new Type($type->getBuiltinType(), $type->isNullable(), $type->getClassName(), true, $type->getCollectionKeyTypes(), $type->getCollectionValueTypes());
55-
}
56-
}
57-
58-
$propertyMetadata = $propertyMetadata->withBuiltinTypes($types);
46+
if (!$propertyMetadata->getPhpType()) {
47+
$propertyMetadata = $propertyMetadata->withPhpType($this->propertyInfo->getType($resourceClass, $property, $options));
5948
}
6049

6150
if (null === $propertyMetadata->getDescription() && null !== $description = $this->propertyInfo->getShortDescription($resourceClass, $property, $options)) {

0 commit comments

Comments
 (0)