Idiomas: 🇬🇧 English | 🇨🇳 简体中文 | 🇪🇸 Español | 🇵🇹 Português | 🇷🇺 Русский | 🇺🇦 Українська
Este paquete genera un documento OpenAPI a partir de rutas Symfony, firmas de controladores, atributos Symfony HttpKernel y clases DTO/View tipadas.
El objetivo es mantener la documentación de la API cerca del código de la aplicación. Los endpoints normales no deberían necesitar grandes bloques #[OA\...]. Las rutas describen paths y methods, los atributos Symfony describen request mapping, los DTO describen datos de entrada, los view objects describen datos de salida y las route options describen operation metadata. Los fragmentos OpenAPI manuales quedan disponibles para casos excepcionales.
La API pública vive en el namespace Sunrise\Symfony\OpenApi.
Lecturas adicionales:
- Artículo en Medium explica qué problema resuelve este paquete y cómo usarlo en una aplicación Symfony.
- PHP Annotations plugin para PhpStorm debería soportar aliases para los attributes de este paquete en próximas versiones, lo que puede mejorar IDE completion y navigation.
composer require sunrise-studio/symfony-openapiEl paquete requiere PHP 8.2 o más nuevo. Las versiones soportadas de los componentes Symfony están definidas en composer.json. Symfony 8.1 o más nuevo solo es necesario si tu aplicación quiere usar el atributo runtime nativo #[Serialize].
Registra el bundle:
// config/bundles.php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Sunrise\Symfony\OpenApi\OpenApiBundle::class => ['all' => true],
];Importa las rutas del paquete:
# config/routes.yaml
openapi:
resource: '@OpenApiBundle/config/routes.php'Esto importa dos rutas de documentación:
| Route | Controller | Propósito |
|---|---|---|
GET /docs |
SwaggerController |
Sirve Swagger UI configurado para leer /docs/openapi.json. |
GET /docs/openapi.json |
DocumentController |
Sirve el documento OpenAPI JSON generado. |
Estas rutas no se incluyen en el API document generado porque no tienen api: true y sus paths no empiezan con /api/.
Si solo necesitas una ruta, importa su archivo directamente:
openapi_document:
resource: '@OpenApiBundle/config/routes/document.php'
swagger_ui:
resource: '@OpenApiBundle/config/routes/swagger.php'Referencias Symfony:
Configuración típica de una aplicación:
# config/packages/openapi.yaml
parameters:
openapi.initial_document:
openapi: 3.1.1
info:
title: API
version: 1.0.0
openapi.default_timestamp_format: !php/const DateTimeInterface::RFC3339_EXTENDEDParámetros útiles:
| Parámetro | Default | Propósito |
|---|---|---|
openapi.initial_document |
OpenAPI version + API title |
Documento base que se fusiona con los generated paths y schemas. |
openapi.initial_operation |
responses: [] |
Operation base que se fusiona con cada generated operation. |
openapi.document_filename |
%kernel.project_dir%/var/openapi.json |
Output file usado por openapi:build-document. |
openapi.document_uri |
/docs/openapi.json |
Public URI del documento generado. Swagger UI lo usa para cargar el documento. |
openapi.default_timestamp_format |
OpenApiConfiguration::DEFAULT_TIMESTAMP_FORMAT |
Formato PHP date() usado para generar valores OpenAPI example para schemas de fecha/hora. |
SwaggerConfiguration puede reemplazarse como servicio si necesitas Swagger UI assets o template variables.
Si solo Swagger UI necesita otro path, define la ruta tú mismo:
# config/routes.yaml
swagger_ui:
path: /swagger.html
controller: Sunrise\Symfony\OpenApi\Controller\SwaggerController
methods: [GET]
options:
api: falseSi también cambia la route del OpenAPI document, actualiza la ruta y openapi.document_uri para que Swagger UI cargue el documento correcto:
# config/routes.yaml
openapi_document:
path: /openapi.json
controller: Sunrise\Symfony\OpenApi\Controller\DocumentController
methods: [GET]
options:
api: false# config/packages/openapi.yaml
parameters:
openapi.document_uri: /openapi.jsonEjecuta:
php bin/console openapi:build-documentEl comando lee la colección de rutas, conserva las routes que deben documentarse, construye el OpenAPI document y lo escribe en openapi.document_filename.
Después de generar, con las rutas por defecto del paquete importadas:
/docsabre Swagger UI./docs/openapi.jsondevuelve el JSON document generado.
Route options son el lugar por defecto para operation metadata:
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/pets', options: ['tags' => ['Pets']])]
final readonly class PetController
{
#[Route('/{id}', methods: ['GET'], options: [
'summary' => 'Finds pet by ID',
'description' => 'Returns one pet.',
'deprecated' => false,
])]
public function show(int $id): PetView
{
// ...
}
}Options soportadas:
| Option | Type | Propósito |
|---|---|---|
tag, tags |
string|string[] |
OpenAPI operation tags. |
summary |
string |
OpenAPI operation summary. |
description |
string |
OpenAPI operation description. |
deprecated, is_deprecated, isDeprecated |
bool |
Marca una operation como deprecated. |
api, is_api, isApi |
bool |
Incluye o excluye la route del generated document. |
response_code |
int |
Documented response status cuando #[Serialize] no proporciona uno. Defaults: 200 para response body y 204 para void explícito. |
response_format |
string |
Response format para el documented response body, convertido a media type, por ejemplo json a application/json. |
response_formats |
string[] |
Varios response formats. Se ignora cuando response_format está definido. |
Si no se define ninguna API option, las routes cuyo path empieza con /api/ se tratan como API routes.
Si tu proyecto no quiere guardar tags, summaries, descriptions y API filtering en route options, reemplaza RouteMetadataResolverInterface.
El paquete entiende los Symfony controller attributes que describen request data. Consulta la documentación de Symfony.
Symfony path variables se leen desde las rutas compiladas. Requirements se convierten en OpenAPI schema patterns.
#[Route('/api/pets/{petId}', requirements: ['petId' => '\d+'])]
public function show(int $petId): PetView
{
// ...
}Reflected parameter types soportados para path variables:
boolintfloatstringBackedEnumDateTimeInterfaceSymfony\Component\Uid\AbstractUid
Symfony route mapping aliases se soportan para mappings simples como ['id' => 'petId']. Entity-style mappings como {id:pet.id} no se describen como object schemas; la path variable pública se documenta como string si no se encuentra un scalar parameter soportado.
#[MapQueryParameter] describe scalar, enum, date/time, UID o array query parameters.
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
public function find(
#[MapQueryParameter] PetStatus $status,
#[MapQueryParameter] string ...$tags,
): JsonResponse {
// ...
}Los variadic parameters se describen como arrays y no se marcan como required.
#[MapQueryString] describe un query object.
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
public function list(#[MapQueryString] PetSearchQuery $query): JsonResponse
{
// ...
}Sin key, el nombre del parámetro es el nombre del parámetro PHP y el objeto usa style: form. Con key, ese valor se usa como nombre del parámetro y el objeto usa style: deepObject.
Note
Las versiones de Symfony anteriores a 7.3 no contienen el argumento MapQueryString::key. Consulta el Symfony 7.3 HttpKernel changelog y las Symfony 7.3 DX improvements.
#[MapRequestPayload] crea un OpenAPI requestBody.
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
public function create(#[MapRequestPayload(acceptFormat: 'json')] CreatePetRequest $request): PetView
{
// ...
}Request body generado:
- El PHP parameter type se convierte en request schema.
acceptFormates opcional. Si se omite, se usa el route default_format; si_formattampoco existe, se usajson.acceptFormatse convierte desde Symfony request format a media type, por ejemplojsonaapplication/json.- Si el PHP parameter es required, el OpenAPI request body también es required.
- Para array payloads,
MapRequestPayload(type: SomeDto::class)describe el item type.
Note
Las versiones de Symfony anteriores a 7.1 no contienen el argumento MapRequestPayload::type. Consulta el Symfony 7.1 HttpKernel changelog.
#[MapUploadedFile] añade un multipart/form-data request body con binary fields.
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpKernel\Attribute\MapUploadedFile;
public function upload(#[MapUploadedFile(name: 'photo')] UploadedFile $file): JsonResponse
{
// ...
}Los variadic uploaded files se describen como un array de binary strings y no se marcan como required.
Note
Las versiones de Symfony anteriores a 7.1 no contienen el atributo MapUploadedFile. Consulta el Symfony 7.1 HttpKernel changelog y el anuncio de Symfony 7.1 MapUploadedFile.
#[MapDateTime(format: ...)] cambia el generated date/time example para controller parameters.
use Symfony\Component\HttpKernel\Attribute\MapDateTime;
public function history(#[MapDateTime(format: 'Y-m-d')] DateTimeImmutable $date): JsonResponse
{
// ...
}El argumento format es opcional. Si se omite, se usa el default timestamp format.
Escribe los controller return types como debe verse la API pública.
| Return type del controlador | Generated response |
|---|---|
| View object, DTO, scalar, array | JSON response body. La schema se lee del return type del método. Status por defecto 200. |
void explícito |
Empty response. Status por defecto 204. |
Symfony Response subclass |
Response body no se genera automáticamente. Usa #[Operation] si la response debe documentarse manualmente. |
Para APIs JSON, no necesitas response format option. Usa route options solo cuando los defaults no encajan con el endpoint:
response_codecambia el documented status, por ejemplo201para create actions.response_formatdocumenta un response format no estándar.response_formatsdocumenta varios response formats.
#[Route('/api/pets/{id}', methods: ['GET'])]
public function show(int $id): PetView
{
// ...
}Symfony 8.1 introdujo #[Serialize], que serializa controller results en runtime. Cuando ese atributo está presente, el paquete lee Serialize::code; la schema sigue saliendo del PHP return type.
use Symfony\Component\HttpKernel\Attribute\Serialize;
#[Route('/api/pets', methods: ['POST'], options: ['response_code' => 201])]
#[Serialize(code: 201)]
public function create(CreatePetRequest $request): PetView
{
// ...
}Usa un return type explícito void para actions sin response body:
#[Route('/api/pets/{id}', methods: ['DELETE'])]
public function delete(int $id): void
{
// ...
}Esto documenta el endpoint como una response vacía 204. Symfony no convierte por sí mismo un resultado null del controlador en 204, así que las aplicaciones que usan actions void deben resolverlo en runtime.
Si la aplicación todavía no puede usar Symfony 8.1, un pequeño listener para KernelEvents::VIEW puede cubrir ambos casos: null se convierte en 204, y otros controller results se serializan como JSON. La implementación de Symfony es SerializeControllerResultAttributeListener; una versión mínima JSON-only puede ser así:
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Serializer\SerializerInterface;
#[AsEventListener(event: KernelEvents::VIEW)]
final readonly class JsonControllerResultListener
{
public function __construct(
private SerializerInterface $serializer,
) {
}
public function __invoke(ViewEvent $event): void
{
$result = $event->getControllerResult();
if ($result === null) {
$event->setResponse(new Response(status: 204));
return;
}
$event->setResponse(new JsonResponse(
$this->serializer->serialize($result, 'json'),
200,
json: true,
));
}
}Si tu proyecto tiene reglas distintas para response status o formats, reemplaza ResponseMetadataResolverInterface.
El paquete proporciona pequeños OpenAPI attributes para casos donde los PHP types no son suficientes:
| Attribute | Target | Propósito |
|---|---|---|
#[Operation] |
class, method | Añade un manual OpenAPI operation fragment. |
#[ItemType] |
property, parameter | Describe array item type. |
#[SchemaName] |
class | Sobrescribe component schema name. |
#[PropertyName] |
property | Sobrescribe OpenAPI property name. |
#[IgnoreProperty] |
property | Excluye una property del object schema. |
#[TimestampFormat] |
property | Sobrescribe date/time example format. |
Array item types normalmente se leen desde PHPDoc:
/** @var list<PetView> */
public array $pets;Se soportan PetView[], list<PetView>, array<PetView> y array<string, PetView>. Nullable item types como array<PetView|null> también se soportan. Item types demasiado amplios o ambiguos, como array<mixed> y array<PetView|ErrorView>, se ignoran. Usa #[ItemType] cuando necesites una sobrescritura explícita o un item limit; tiene prioridad sobre @var.
La mayoría de endpoints no deberían necesitar OpenAPI fragments manuales. Para casos excepcionales, usa #[Operation]:
use Sunrise\Symfony\OpenApi\Annotation\Operation;
use Sunrise\Symfony\OpenApi\Type;
#[Operation([
'responses' => [
200 => [
'description' => 'A list of pets.',
'content' => [
'application/json' => [
'schema' => [
'type' => 'array',
'items' => new Type(PetView::class),
],
],
],
],
],
])]
public function list(): JsonResponse
{
// ...
}El fragment se fusiona con la generated operation.
Recomendamos mantener el API predecible: una action exitosa debe devolver un view object documentado, y los errores deben usar una error shape documentada en lugar de quedar ocultos en ramas del controller.
Para un error response común, describe un default response con #[Operation] o con openapi.initial_operation:
# config/packages/openapi.yaml
parameters:
openapi.initial_operation:
responses:
default:
description: The operation was unsuccessful.
content:
application/json:
schema: App\View\ErrorViewuse App\View\ErrorView;
use Sunrise\Symfony\OpenApi\Annotation\Operation;
use Sunrise\Symfony\OpenApi\Type;
#[Operation([
'responses' => [
'default' => [
'description' => 'The operation was unsuccessful.',
'content' => [
'application/json' => [
'schema' => new Type(ErrorView::class),
],
],
],
],
])]
final readonly class PetController
{
}En arrays YAML/PHP, el valor de schema puede ser un PHP type string. Se trata como PHP type cuando el string contiene \. Para una clase sin namespace, usa un backslash inicial, por ejemplo \AppErrorView. En PHP attributes, usa new Type(ErrorView::class) cuando necesites un type object explícito.
La generación de schemas por defecto cubre PHP types comunes:
BoolPhpTypeSchemaResolverIntPhpTypeSchemaResolverFloatPhpTypeSchemaResolverStringPhpTypeSchemaResolverArrayPhpTypeSchemaResolverArrayAccessPhpTypeSchemaResolverBackedEnumPhpTypeSchemaResolverObjectPhpTypeSchemaResolverSymfonyUidPhpTypeSchemaResolverSunrise\Symfony\OpenApi\PhpTypeSchemaResolver\TimestampPhpTypeSchemaResolver
Si tu proyecto tiene un PHP type custom que necesita un custom schema, implementa OpenApiPhpTypeSchemaResolverInterface. Con autoconfigure habilitado, el resolver se registrará automáticamente. En caso contrario, añádele el tag openapi.php_type_schema_resolver.
DTOs y view objects se describen desde typed properties.
Lee PHP classes directamente:
- se soportan instantiable non-internal classes;
- se reflejan public, protected y private properties;
- property types se convierten en OpenAPI property schemas;
- properties sin default value se marcan como required;
- scalar y backed enum default values se emiten en el schema;
- se soportan constructor-promoted property defaults;
#[SchemaName]cambia component schema name;#[PropertyName]cambia property name;#[IgnoreProperty]excluye una property;- array item types se leen desde
@varcuando es posible; #[ItemType]describe array properties explícitamente y tiene prioridad sobre@var;#[TimestampFormat]cambia date/time examples.
Este resolver no usa Symfony Serializer metadata. No lee serializer groups, getters, setters, SerializedName, name converters ni reglas de conversión camelCase/snake_case.
Recomendamos DTO y View classes explícitas con typed properties. Si necesitas otra forma externa, crea un nuevo View object y mapea tu domain object a él. Esto mantiene simples la búsqueda, el refactoring y la schema generation.
Si tu equipo necesita first-class Symfony Serializer support, abre un issue. Consideraremos añadirlo como optional resolver o strategy.
Referencia Symfony Serializer: Serializer.
El paquete está formado por servicios reemplazables para proyectos con convenciones propias:
| Service/interface | Propósito | Tag |
|---|---|---|
RouteMetadataResolverInterface |
Controla tags, summary, description, deprecation y API filtering. | — |
ResponseMetadataResolverInterface |
Controla response status codes y response formats. | — |
OpenApiOperationEnricherInterface |
Añade request parameters, request bodies, responses o custom operation data. | openapi.operation_enricher (se añade automáticamente con autoconfigure) |
OpenApiPhpTypeSchemaResolverInterface |
Convierte PHP types en OpenAPI schemas. | openapi.php_type_schema_resolver (se añade automáticamente con autoconfigure) |
OpenApiPathBuilderInterface |
Convierte Symfony route paths en OpenAPI paths. | — |
Reemplaza estos servicios en el Symfony container cuando las reglas del proyecto difieran de los defaults.


