diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index cf6b678c1..6b00eaa0a 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -92,10 +92,11 @@ public function describe(Model $model, OA\Schema $schema): void $schema->type = 'object'; $mapping = false; + $attributesMetadata = []; if (null !== $this->classMetadataFactory) { - $mapping = $this->classMetadataFactory - ->getMetadataFor($class) - ->getClassDiscriminatorMapping(); + $classMetadata = $this->classMetadataFactory->getMetadataFor($class); + $mapping = $classMetadata->getClassDiscriminatorMapping(); + $attributesMetadata = $classMetadata->getAttributesMetadata(); } if ($mapping && Generator::UNDEFINED === $schema->discriminator) { @@ -158,7 +159,17 @@ public function describe(Model $model, OA\Schema $schema): void throw new \LogicException(\sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `#[OA\Property(type="")]` to make its type explicit.', $class, $propertyName)); } - $this->describeProperty($types, $model, $property, $propertyName); + $propertyContext = $model->getSerializationContext(); + if (isset($attributesMetadata[$propertyName])) { + $propertyContext = array_merge( + $propertyContext, + $attributesMetadata[$propertyName]->getNormalizationContextForGroups( + array_filter($groups ?? [], 'is_string') + ) + ); + } + + $this->describeProperty($types, $model, $property, $propertyName, $propertyContext); } $this->markRequiredProperties($schema); @@ -193,15 +204,18 @@ private function camelize(string $string): string } /** - * @param LegacyType[]|Type $types + * @param LegacyType[]|Type $types + * @param array $propertyContext */ - private function describeProperty(array|Type $types, Model $model, OA\Schema $property, string $propertyName): void + private function describeProperty(array|Type $types, Model $model, OA\Schema $property, string $propertyName, array $propertyContext = []): void { + $context = [] !== $propertyContext ? $propertyContext : $model->getSerializationContext(); + if ($this->propertyDescriber instanceof ModelRegistryAwareInterface) { $this->propertyDescriber->setModelRegistry($this->modelRegistry); } - if ($this->propertyDescriber->supports($types, $model->getSerializationContext())) { - $this->propertyDescriber->describe($types, $property, $model->getSerializationContext()); + if ($this->propertyDescriber->supports($types, $context)) { + $this->propertyDescriber->describe($types, $property, $context); return; } diff --git a/tests/Functional/Controller/ContextAttributeController.php b/tests/Functional/Controller/ContextAttributeController.php new file mode 100644 index 000000000..f8f2dc43d --- /dev/null +++ b/tests/Functional/Controller/ContextAttributeController.php @@ -0,0 +1,32 @@ + [ 'CustomModelNameController', ]; + + yield 'Context attribute on properties' => [ + 'ContextAttributeController', + null, + [], + [ + 'framework' => [ + 'property_info' => [ + 'enabled' => true, + ], + 'serializer' => [ + 'enabled' => true, + 'enable_attributes' => true, + ], + 'validation' => [ + 'enabled' => true, + 'enable_attributes' => true, + 'static_method' => [ + 'loadValidatorMetadata', + ], + 'translation_domain' => 'validators', + 'email_validation_mode' => 'html5', + 'mapping' => [ + 'paths' => [], + ], + 'not_compromised_password' => [ + 'enabled' => true, + 'endpoint' => null, + ], + 'auto_mapping' => [], + ], + ], + ], + ]; } /** diff --git a/tests/Functional/Entity/EntityWithContext.php b/tests/Functional/Entity/EntityWithContext.php new file mode 100644 index 000000000..2225b86e9 --- /dev/null +++ b/tests/Functional/Entity/EntityWithContext.php @@ -0,0 +1,25 @@ + true])] + public ArticleType81 $typeWithForceNames; +} diff --git a/tests/Functional/Fixtures/ContextAttributeController.json b/tests/Functional/Fixtures/ContextAttributeController.json new file mode 100644 index 000000000..9fbc2a9c9 --- /dev/null +++ b/tests/Functional/Fixtures/ContextAttributeController.json @@ -0,0 +1,59 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "", + "version": "0.0.0" + }, + "paths": { + "/context-attribute": { + "get": { + "operationId": "get_nelmio_apidoc_tests_functional_contextattribute_context", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityWithContext" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ArticleType81": { + "type": "string", + "enum": [ + "draft", + "final" + ] + }, + "ArticleType812": { + "type": "string", + "enum": [ + "DRAFT", + "FINAL" + ] + }, + "EntityWithContext": { + "required": [ + "type", + "typeWithForceNames" + ], + "properties": { + "type": { + "$ref": "#/components/schemas/ArticleType81" + }, + "typeWithForceNames": { + "$ref": "#/components/schemas/ArticleType812" + } + }, + "type": "object" + } + } + } +} \ No newline at end of file