Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/ModelDescriber/ObjectModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -193,15 +204,18 @@ private function camelize(string $string): string
}

/**
* @param LegacyType[]|Type $types
* @param LegacyType[]|Type $types
* @param array<string, mixed> $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();

Comment on lines +210 to +213
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

describeProperty() treats an explicitly provided empty $propertyContext as “not provided” due to $context = $propertyContext ?: $model->getSerializationContext();. This makes it impossible to intentionally override the model context with an empty context and also doesn’t match the docblock wording (“used instead of the model-level context when provided”). Consider changing the parameter to ?array $propertyContext = null and using ?? to distinguish null from an intentionally empty array (or remove the default and always pass a context).

Copilot uses AI. Check for mistakes.
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;
}
Expand Down
32 changes: 32 additions & 0 deletions tests/Functional/Controller/ContextAttributeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;

use Nelmio\ApiDocBundle\Attribute\Model;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithContext;
use OpenApi\Attributes as OA;
use Symfony\Component\Routing\Attribute\Route;

class ContextAttributeController
{
#[OA\Response(
response: '200',
description: 'Success',
content: new Model(type: EntityWithContext::class),
)]
#[Route('/context-attribute', methods: ['GET'])]
public function contextAction(): void
{
}
}
34 changes: 34 additions & 0 deletions tests/Functional/ControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,40 @@ static function (RoutingConfigurator $routes) {
yield 'Custom model names' => [
'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' => [],
],
],
],
];
}

/**
Expand Down
25 changes: 25 additions & 0 deletions tests/Functional/Entity/EntityWithContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

use Nelmio\ApiDocBundle\ModelDescriber\EnumModelDescriber;
use Symfony\Component\Serializer\Attribute\Context;

class EntityWithContext
{
public ArticleType81 $type;

#[Context([EnumModelDescriber::FORCE_NAMES => true])]
public ArticleType81 $typeWithForceNames;
}
59 changes: 59 additions & 0 deletions tests/Functional/Fixtures/ContextAttributeController.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
Loading