diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f930e2637c1..75b7faabaca 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -191,3 +191,28 @@ parameters: identifier: function.alreadyNarrowedType path: tests/Symfony/Bundle/Command/DebugResourceCommandTest.php reportUnmatched: false + + # Structural assertions that document the ApiProperty internal API surface + # (see #8173). PHPStan correctly sees the methods as defined on this branch, + # but the assertions guard against accidental removal in future releases. + - + identifier: function.alreadyNarrowedType + path: src/Metadata/Tests/Property/Factory/InternalBuiltinTypesDeprecationTest.php + reportUnmatched: false + + # Split-package consumers of ApiPlatform\Metadata guard internal accessors with + # method_exists() so the "lowest" dependency builds (api-platform/metadata <= 4.3.7, + # which lacks internalGetBuiltinTypes()) still install. PHPStan sees the method + # as always-defined when analysing the monorepo, hence the narrowed-type notice. + - + identifier: function.alreadyNarrowedType + paths: + - src/Elasticsearch + - src/GraphQl + - src/Hal + - src/Hydra + - src/JsonApi + - src/JsonSchema + - src/Serializer + - src/Symfony/Validator + reportUnmatched: false diff --git a/src/Elasticsearch/Filter/AbstractFilter.php b/src/Elasticsearch/Filter/AbstractFilter.php index a305a57e03b..a86449d4fc9 100644 --- a/src/Elasticsearch/Filter/AbstractFilter.php +++ b/src/Elasticsearch/Filter/AbstractFilter.php @@ -201,7 +201,7 @@ protected function getLegacyMetadata(string $resourceClass, string $property): a return $noop; } - $types = $propertyMetadata->getBuiltinTypes(); + $types = method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes(); if (null === $types) { return $noop; diff --git a/src/Elasticsearch/Util/FieldDatatypeTrait.php b/src/Elasticsearch/Util/FieldDatatypeTrait.php index 25a0fe81bc8..c5dc56d4999 100644 --- a/src/Elasticsearch/Util/FieldDatatypeTrait.php +++ b/src/Elasticsearch/Util/FieldDatatypeTrait.php @@ -69,7 +69,7 @@ private function getNestedFieldPath(string $resourceClass, string $property): ?s } if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; foreach ($types as $type) { if ( diff --git a/src/GraphQl/Resolver/Factory/ResolverFactory.php b/src/GraphQl/Resolver/Factory/ResolverFactory.php index 074728a53a9..dfec185b404 100644 --- a/src/GraphQl/Resolver/Factory/ResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ResolverFactory.php @@ -69,7 +69,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul return $body; } } else { - $type = $propertyMetadata?->getBuiltinTypes()[0] ?? null; + $type = ($propertyMetadata && method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata?->getBuiltinTypes())[0] ?? null; // Data already fetched and normalized (field or nested resource) if ($body || null === $resourceClass || ($type && !$type->isCollection())) { diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index 4d674368723..b3def48855b 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -222,7 +222,7 @@ public function getResourceObjectTypeFields(?string $resourceClass, Operation $o $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $context); if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $propertyTypes = $propertyMetadata->getBuiltinTypes(); + $propertyTypes = method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes(); if ( !$propertyTypes diff --git a/src/Hal/Serializer/ItemNormalizer.php b/src/Hal/Serializer/ItemNormalizer.php index b4ff28d5ce7..52b91dde897 100644 --- a/src/Hal/Serializer/ItemNormalizer.php +++ b/src/Hal/Serializer/ItemNormalizer.php @@ -192,7 +192,7 @@ private function getComponents(object $object, ?string $format, array $context): /** @var class-string|null $className */ $className = null; } else { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; } // prevent declaring $attribute as attribute if it's already declared as relationship diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index 428c6da7a94..3151a879179 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -419,7 +419,7 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null } // TODO: remove in 5.x } else { - $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? []; + $builtInTypes = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; foreach ($builtInTypes as $type) { if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) { @@ -501,7 +501,7 @@ private function isSingleRelation(ApiProperty $propertyMetadata): bool } // TODO: remove in 5.x - $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? []; + $builtInTypes = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; foreach ($builtInTypes as $type) { $className = $type->getClassName(); diff --git a/src/JsonApi/JsonSchema/SchemaFactory.php b/src/JsonApi/JsonSchema/SchemaFactory.php index 3f71cf0e243..76bbbdbea75 100644 --- a/src/JsonApi/JsonSchema/SchemaFactory.php +++ b/src/JsonApi/JsonSchema/SchemaFactory.php @@ -383,7 +383,7 @@ private function getRelationship(string $resourceClass, string $property, ?array $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $serializerContext ?? []); if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; $isRelationship = false; $isOne = $isMany = false; $relatedClasses = []; diff --git a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php index c20fedffd09..a87b094f9d5 100644 --- a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php +++ b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php @@ -95,7 +95,7 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio } if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; + $type = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes())[0] ?? null; if ($type && null !== $type->getClassName()) { return "data/relationships/$fieldName"; } diff --git a/src/JsonApi/Serializer/ItemNormalizer.php b/src/JsonApi/Serializer/ItemNormalizer.php index b97c1411dc2..8f39291e342 100644 --- a/src/JsonApi/Serializer/ItemNormalizer.php +++ b/src/JsonApi/Serializer/ItemNormalizer.php @@ -396,7 +396,7 @@ private function getComponents(object $object, ?string $format, array $context): $isRelationship = false; if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; foreach ($types as $type) { $isOne = $isMany = false; diff --git a/src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php b/src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php index 28e57b84c39..462d78ad690 100644 --- a/src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php +++ b/src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php @@ -344,7 +344,7 @@ private function getClassSchemaDefinition(?string $className, ?bool $readableLin private function getLegacyTypeSchema(ApiProperty $propertyMetadata, array $propertySchema, string $resourceClass, string $property, ?bool $link): array { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; $className = ($types[0] ?? null)?->getClassName() ?? null; if (null !== $propertyMetadata->getUriTemplate() || (!\array_key_exists('readOnly', $propertySchema) && false === $propertyMetadata->isWritable() && !$propertyMetadata->isInitializable()) && !$className) { diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 7014020afc4..da6363f09ba 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -194,7 +194,7 @@ private function buildLegacyPropertySchema(Schema $schema, string $definitionNam return; } - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; // never override the following keys if at least one is already set // or if property has no type(s) defined diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index 28c6d09870d..6605623b171 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -534,6 +534,29 @@ public function getBuiltinTypes(): ?array { trigger_deprecation('api-platform/metadata', '4.2', 'The "%s()" method is deprecated, use "%s::getNativeType()" instead.', __METHOD__, self::class); + return $this->internalGetBuiltinTypes(); + } + + /** + * deprecated since 4.2, use "withNativeType" instead. + * + * @param LegacyType[] $builtinTypes + */ + public function withBuiltinTypes(array $builtinTypes = []): static + { + trigger_deprecation('api-platform/metadata', '4.2', 'The "%s()" method is deprecated, use "%s::withNativeType()" instead.', __METHOD__, self::class); + + return $this->internalWithBuiltinTypes($builtinTypes); + } + + /** + * @internal Same as {@see getBuiltinTypes()} but without the deprecation; used by API Platform's + * own factories that still need the legacy representation on symfony/property-info < 7.1. + * + * @return LegacyType[]|null + */ + public function internalGetBuiltinTypes(): ?array + { if (null === $this->builtinTypes && null !== $this->nativeType) { $this->builtinTypes = PropertyInfoToTypeInfoHelper::convertTypeToLegacyTypes($this->nativeType) ?? []; } @@ -542,14 +565,13 @@ public function getBuiltinTypes(): ?array } /** - * deprecated since 4.2, use "withNativeType" instead. + * @internal Same as {@see withBuiltinTypes()} but without the deprecation; used by API Platform's + * own factories that still produce legacy types on symfony/property-info < 7.1. * * @param LegacyType[] $builtinTypes */ - public function withBuiltinTypes(array $builtinTypes = []): static + public function internalWithBuiltinTypes(array $builtinTypes = []): static { - trigger_deprecation('api-platform/metadata', '4.2', 'The "%s()" method is deprecated, use "%s::withNativeType()" instead.', __METHOD__, self::class); - $self = clone $this; $self->builtinTypes = $builtinTypes; $self->nativeType = PropertyInfoToTypeInfoHelper::convertLegacyTypesToType($builtinTypes); diff --git a/src/Metadata/IdentifiersExtractor.php b/src/Metadata/IdentifiersExtractor.php index 7c0c8c5c98c..018c9c89a62 100644 --- a/src/Metadata/IdentifiersExtractor.php +++ b/src/Metadata/IdentifiersExtractor.php @@ -133,7 +133,7 @@ private function getIdentifierValue(object $item, string $class, string $propert // TODO: remove in 5.x if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes(); + $types = $propertyMetadata->internalGetBuiltinTypes(); if (null === ($type = $types[0] ?? null)) { continue; } diff --git a/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php b/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php index 5e53c08e049..a868a5fd84d 100644 --- a/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php @@ -132,8 +132,8 @@ private function createMetadata(ApiProperty $attribute, ?ApiProperty $propertyMe continue; } - if ($builtinTypes = $attribute->getBuiltinTypes()) { - $propertyMetadata = $propertyMetadata->withBuiltinTypes($builtinTypes); + if ($builtinTypes = $attribute->internalGetBuiltinTypes()) { + $propertyMetadata = $propertyMetadata->internalWithBuiltinTypes($builtinTypes); } continue; diff --git a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php index dad89450030..64d527ed589 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php @@ -66,7 +66,7 @@ public function create(string $resourceClass, string $property, array $options = continue; } - $apiProperty = $apiProperty->withBuiltinTypes(array_map(static fn (string $builtinType): LegacyType => new LegacyType($builtinType), $value)); + $apiProperty = $apiProperty->internalWithBuiltinTypes(array_map(static fn (string $builtinType): LegacyType => new LegacyType($builtinType), $value)); continue; } @@ -118,6 +118,13 @@ private function update(ApiProperty $propertyMetadata, array $metadata): ApiProp { foreach (get_class_methods(ApiProperty::class) as $method) { if (preg_match('/^(?:get|is)(.*)/', (string) $method, $matches) && null !== ($val = $metadata[lcfirst($matches[1])] ?? null) && method_exists($propertyMetadata, "with{$matches[1]}")) { + // BC layer, to remove in 5.0: route the deprecated builtinTypes setter to the internal one. + if ('BuiltinTypes' === $matches[1]) { + $propertyMetadata = $propertyMetadata->internalWithBuiltinTypes($val); + + continue; + } + $propertyMetadata = $propertyMetadata->{"with{$matches[1]}"}($val); } } diff --git a/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php b/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php index ba9b24b8adf..edbd657f64c 100644 --- a/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php @@ -48,7 +48,7 @@ public function create(string $resourceClass, string $property, array $options = // TODO: remove in 5.x if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - if (!$propertyMetadata->getBuiltinTypes()) { + if (!$propertyMetadata->internalGetBuiltinTypes()) { $types = $this->propertyInfo->getTypes($resourceClass, $property, $options) ?? []; // @phpstan-ignore-line foreach ($types as $i => $type) { @@ -58,7 +58,7 @@ public function create(string $resourceClass, string $property, array $options = } } - $propertyMetadata = $propertyMetadata->withBuiltinTypes($types); + $propertyMetadata = $propertyMetadata->internalWithBuiltinTypes($types); } } else { if (!$propertyMetadata->getNativeType()) { diff --git a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php index d7412a59268..2999e1f6058 100644 --- a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php @@ -76,7 +76,7 @@ public function create(string $resourceClass, string $property, array $options = // TODO: remove in 5.x if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = $propertyMetadata->internalGetBuiltinTypes() ?? []; if (!$this->isResourceClass($resourceClass) && $types) { foreach ($types as $builtinType) { diff --git a/src/Metadata/Tests/Property/Factory/InternalBuiltinTypesDeprecationTest.php b/src/Metadata/Tests/Property/Factory/InternalBuiltinTypesDeprecationTest.php new file mode 100644 index 00000000000..50b7eabad43 --- /dev/null +++ b/src/Metadata/Tests/Property/Factory/InternalBuiltinTypesDeprecationTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata\Tests\Property\Factory; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Property\Factory\AttributePropertyMetadataFactory; +use ApiPlatform\Metadata\Property\Factory\ExtractorPropertyMetadataFactory; +use ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyMetadataFactory; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; + +/** + * Regression test for #8173: internal factories must not emit + * "ApiProperty::withBuiltinTypes()" / "getBuiltinTypes()" deprecations + * when building property metadata. + * + * The public deprecated methods must keep emitting the deprecation for + * userland callers, but ApiPlatform's own factories must use an internal + * code path that does not trigger it. + */ +final class InternalBuiltinTypesDeprecationTest extends TestCase +{ + use ProphecyTrait; + + /** + * @var list + */ + private array $deprecations = []; + + protected function setUp(): void + { + $this->deprecations = []; + // Only record deprecations that target ApiProperty's legacy builtin types accessors: + // the surrounding code (e.g. instantiating Symfony's LegacyType on property-info 7.3+) + // also emits unrelated deprecations that must not influence this test. + set_error_handler(function (int $errno, string $errstr): bool { + if (str_contains($errstr, 'ApiPlatform\Metadata\ApiProperty::')) { + $this->deprecations[] = $errstr; + } + + return true; + }, \E_USER_DEPRECATED); + } + + protected function tearDown(): void + { + restore_error_handler(); + } + + public function testApiPropertyExposesInternalBuiltinTypesAccessors(): void + { + $this->assertTrue(method_exists(ApiProperty::class, 'internalGetBuiltinTypes'), 'ApiProperty must expose an internal getter to read builtin types without triggering the deprecation.'); + $this->assertTrue(method_exists(ApiProperty::class, 'internalWithBuiltinTypes'), 'ApiProperty must expose an internal setter to store builtin types without triggering the deprecation.'); + } + + public function testInternalBuiltinTypesAccessorsDoNotEmitDeprecation(): void + { + $types = class_exists(LegacyType::class) ? [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)] : []; + + $property = (new ApiProperty())->internalWithBuiltinTypes($types); + $property->internalGetBuiltinTypes(); + $this->assertEmpty($this->deprecations, 'The internal builtin types accessors must not emit any deprecation. Got: '.implode("\n", $this->deprecations)); + } + + public function testPublicWithBuiltinTypesStillEmitsDeprecation(): void + { + $types = class_exists(LegacyType::class) ? [new LegacyType(LegacyType::BUILTIN_TYPE_STRING)] : []; + + (new ApiProperty())->withBuiltinTypes($types); + + $found = false; + foreach ($this->deprecations as $message) { + if (str_contains($message, 'ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()')) { + $found = true; + break; + } + } + + $this->assertTrue($found, 'The public withBuiltinTypes() method must still emit a deprecation for external callers.'); + } + + public function testPublicGetBuiltinTypesStillEmitsDeprecation(): void + { + $property = new ApiProperty(); + $property->getBuiltinTypes(); + + $found = false; + foreach ($this->deprecations as $message) { + if (str_contains($message, 'ApiPlatform\Metadata\ApiProperty::getBuiltinTypes()')) { + $found = true; + break; + } + } + + $this->assertTrue($found, 'The public getBuiltinTypes() method must still emit a deprecation for external callers.'); + } + + public function testAttributePropertyMetadataFactorySourceUsesInternalAccessors(): void + { + $source = file_get_contents((new \ReflectionClass(AttributePropertyMetadataFactory::class))->getFileName()); + + $this->assertStringNotContainsString('->withBuiltinTypes(', $source, 'AttributePropertyMetadataFactory must not call the deprecated public withBuiltinTypes() on ApiProperty.'); + $this->assertStringNotContainsString('->getBuiltinTypes(', $source, 'AttributePropertyMetadataFactory must not call the deprecated public getBuiltinTypes() on ApiProperty.'); + } + + public function testExtractorPropertyMetadataFactorySourceUsesInternalAccessors(): void + { + $source = file_get_contents((new \ReflectionClass(ExtractorPropertyMetadataFactory::class))->getFileName()); + + $this->assertStringNotContainsString('->withBuiltinTypes(', $source, 'ExtractorPropertyMetadataFactory must not call the deprecated public withBuiltinTypes() on ApiProperty.'); + $this->assertStringNotContainsString('->getBuiltinTypes(', $source, 'ExtractorPropertyMetadataFactory must not call the deprecated public getBuiltinTypes() on ApiProperty.'); + } + + public function testPropertyInfoPropertyMetadataFactorySourceUsesInternalAccessors(): void + { + $source = file_get_contents((new \ReflectionClass(PropertyInfoPropertyMetadataFactory::class))->getFileName()); + + $this->assertStringNotContainsString('->withBuiltinTypes(', $source, 'PropertyInfoPropertyMetadataFactory must not call the deprecated public withBuiltinTypes() on ApiProperty.'); + $this->assertStringNotContainsString('->getBuiltinTypes(', $source, 'PropertyInfoPropertyMetadataFactory must not call the deprecated public getBuiltinTypes() on ApiProperty.'); + } +} diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 3c14013ebc2..4f1b3bf4fc9 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -791,7 +791,7 @@ protected function getAttributeValue(object $object, string $attribute, ?string } if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; foreach ($types as $type) { if ( @@ -1126,7 +1126,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value $type = null; if (!method_exists(PropertyInfoExtractor::class, 'getType')) { - $types = $propertyMetadata->getBuiltinTypes() ?? []; + $types = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; } else { $type = $propertyMetadata->getNativeType(); $types = $type instanceof CompositeTypeInterface ? $type->getTypes() : (null === $type ? [] : [$type]); diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php index f23b20a5d8a..d4e2608bb54 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php @@ -106,13 +106,14 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return false; } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $legacyBuiltinTypes = (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []; + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $legacyBuiltinTypes); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_STRING]; } if ( - null !== ($builtinType = ($propertyMetadata->getBuiltinTypes()[0] ?? null)) + null !== ($builtinType = ($legacyBuiltinTypes[0] ?? null)) && $builtinType->isCollection() && \count($builtinType->getCollectionValueTypes()) > 0 ) { diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php index 7d8c9d05428..767fe60330b 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php @@ -52,7 +52,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_INT]; } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php index 0e33eca2ea7..a77cd5af80c 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php @@ -56,7 +56,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_INT]; } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php index 07b2f76d588..3c480b97cb7 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php @@ -59,7 +59,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $constraint instanceof Length && $type?->isIdentifiedBy(TypeIdentifier::STRING); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_STRING]; } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php index f1141818a07..91328038c69 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php @@ -55,7 +55,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_INT]; } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php index 7af4d9f7567..17fd86a48d1 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php @@ -53,7 +53,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_INT]; } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php index 833af136c6d..319d61c8ad1 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php @@ -63,7 +63,7 @@ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); } - $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), (method_exists($propertyMetadata, 'internalGetBuiltinTypes') ? $propertyMetadata->internalGetBuiltinTypes() : $propertyMetadata->getBuiltinTypes()) ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { $types = [LegacyType::BUILTIN_TYPE_INT]; }