NelmioApiDocBundle
The NelmioApiDocBundle bundle allows you to generate documentation in the OpenAPI (Swagger) format and provides a sandbox to interactively experiment with the API.
What's supported?
This bundle supports Symfony route requirements, Symfony request mapping (Symfony attributes), PHP annotations, Swagger-Php annotations, FOSRestBundle annotations and applications using Api-Platform.
For models, it supports the Symfony serializer , the JMS serializer and the willdurand/Hateoas library. It does also support Symfony form types.
Attributes are supported from version 4.7 and PHP 8.1.
Installation
Open a command console, enter your project directory and execute the following command to download the latest version of this bundle:
1
$ composer require nelmio/api-doc-bundle
By default, only routes under /api
are documented. Update the regexp at nelmio_api_doc.areas.path_patterns
in config/packages/nelmio_api_doc.yaml
to change this policy.
Note
If you're not using Flex, then add the bundle to your kernel:
1 2 3 4 5 6 7 8 9 10 11 12 13
class AppKernel extends Kernel
{
public function registerBundles(): iterable
{
$bundles = [
// ...
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
];
// ...
}
}
To browse your documentation with an UI, register one of the following route:
1 2 3 4 5
# config/routes.yaml
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
1 2 3 4 5
# config/routes.yaml
app.redocly:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.redocly }
If you also want to expose it in JSON, register this route:
1 2 3 4 5
# config/routes.yaml
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
As you just installed the bundle, you'll likely see routes you don't want in
your documentation such as /_profiler/
. To fix this, you can filter the
routes that are documented by configuring the bundle:
1 2 3 4 5 6 7
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps (document only routes under /api, except /api/doc)
- ^/api(?!/doc$)
host_patterns: # document only routes with a host of the form api.*
- ^api\.
How does this bundle work?
It generates an OpenAPI documentation from your Symfony app thanks to Describers. One extracts data from SwaggerPHP annotations, one from your routes, etc.
If you configured the app.swagger_ui
route above, you can browse your
documentation at http://example.org/api/doc
.
Using the bundle
You can configure global information in the bundle configuration documentation.info
section (take a look at
the OpenAPI 3.0 specification (formerly Swagger) to know the available fields).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
nelmio_api_doc:
documentation:
servers:
- url: http://api.example.com/unsafe
description: API over HTTP
- url: https://api.example.com/secured
description: API over HTTPS
info:
title: My App
description: This is an awesome app!
version: 1.0.0
x-build: #CommitHash
components:
securitySchemes:
Bearer:
type: http
scheme: bearer
bearerFormat: JWT
security:
- Bearer: []
Note
If you're using Flex, this config is there by default under config/packages/nelmio_api_doc.yaml
. Don't forget to adapt it to your app!
Tip
This configuration field can more generally be used to store your documentation as yaml.
You may find in the .yaml
files from SwaggerPHP examples.
To document your routes, you can use the SwaggerPHP annotations and the
Nelmio\ApiDocBundle\Annotation\Model
annotation in your controllers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
namespace AppBundle\Controller;
use AppBundle\Entity\Reward;
use AppBundle\Entity\User;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Annotations as OA;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
/**
* List the rewards of the specified user.
*
* This call takes into account all confirmed awards, but not pending or refused awards.
*
* @Route("/api/{user}/rewards", methods={"GET"})
* @OA\Response(
* response=200,
* description="Returns the rewards of an user",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref=@Model(type=Reward::class, groups={"full"}))
* )
* )
* @OA\Parameter(
* name="order",
* in="query",
* description="The field used to order rewards",
* @OA\Schema(type="string")
* )
* @OA\Tag(name="rewards")
* @Security(name="Bearer")
*/
public function fetchUserRewardsAction(User $user)
{
// ...
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
namespace AppBundle\Controller;
use AppBundle\Entity\Reward;
use AppBundle\Entity\User;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Attributes as OA;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
/**
* List the rewards of the specified user.
*
* This call takes into account all confirmed awards, but not pending or refused awards.
*/
#[Route('/api/{user}/rewards', methods: ['GET'])]
#[OA\Response(
response: 200,
description: 'Returns the rewards of an user',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: new Model(type: AlbumDto::class, groups: ['full']))
)
)]
#[OA\Parameter(
name: 'order',
in: 'query',
description: 'The field used to order rewards',
schema: new OA\Schema(type: 'string')
)]
#[OA\Tag(name: 'rewards')]
#[Security(name: 'Bearer')]
public function fetchUserRewardsAction(User $user)
{
// ...
}
}
The normal PHPDoc block on the controller method is used for the summary and description.
Tip
Examples of using the annotations can be found in SwaggerPHP examples. However, unlike in those examples, when using this bundle you don't need to specify paths and you can easily document models as well as some other properties described below as they can be automatically be documented using the Symfony integration.
Tip
NelmioApiDocBundle understands symfony's controller attributes. Using these attributes inside your controller allows this bundle to automatically create the necessary documentation. More information can be found here: Symfony attributes.
Use Models
As shown in the example above, the bundle provides the @Model
annotation.
Use it instead of a definition reference and the bundle will deduce your model properties.
Note
A model can be a Symfony form type, a Doctrine ORM entity or a general PHP object.
This annotation has two options:
type
to specify your model's type:
1 2 3 4 5 6
/**
* @OA\Response(
* response=200,
* @Model(type=User::class)
* )
*/
1 2 3 4 5
#[OA\Response(
response: 200,
description: 'Successful response',
content: new Model(type: User::class)
)]
groups
to specify the serialization groups used to (de)serialize your model:
1 2 3 4 5 6
/**
* @OA\Response(
* response=200,
* @Model(type=User::class, groups={"non_sensitive_data"})
* )
*/
1 2 3 4 5
#[OA\Response(
response: 200,
description: 'Successful response',
content: new Model(type: User::class, groups: ['non_sensitive_data'])
)]
groups
may also be used to specify the constraint validation groups used (de)serialize your model, but this must be enabled in configuration:
1 2 3
nelmio_api_doc:
use_validation_groups: true
# ...
With this enabled, groups set in the model will apply to both serializer properties and validator constraints. Take the model class below:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class UserDto
{
/**
* @Groups({"default", "create", "update"})
* @Assert\NotBlank(groups={"default", "create"})
*/
public string $username;
}
1 2 3 4 5 6 7 8 9
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class UserDto
{
#[Groups(["default", "create", "update"])]
#[Assert\NotBlank(groups: ["default", "create"])]
public string $username;
}
The NotBlank
constraint will apply only to the default
and create
group, but not update
. In more practical terms: the username
property
would show as required
for both model create and default, but not update.
When using code generators to build API clients, this often translates into
client side validation and types. NotBlank
adding required
will cause
that property type to not be nullable, for example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use OpenApi\Annotations as OA;
/**
* shows `username` as `required` in the OpenAPI schema (not nullable)
* @OA\Response(
* response=200,
* @Model(type=UserDto::class, groups={"default"})
* )
*/
/**
* Similarly, this will make the username `required` in the create
* schema
* @OA\RequestBody(@Model(type=UserDto::class, groups={"create"}))
*/
/**
* But for updates, the `username` property will not be required
* @OA\RequestBody(@Model(type=UserDto::class, groups={"update"}))
*/
1 2 3 4 5 6 7 8 9 10
use OpenApi\Attributes as OA;
// shows `username` as `required` in the OpenAPI schema (not nullable)
#[OA\Response(response: 200, content: new Model(type: UserDto::class, groups: ["default"]))]
// Similarly, this will make the username `required` in the create schema
#[OA\RequestBody(new Model(type: UserDto::class, groups: ["create"]))]
// But for updates, the `username` property will not be required
#[OA\RequestBody(new Model(type: UserDto::class, groups: ["update"]))]
Tip
When used at the root of @OA\Response
and @OA\Parameter
, @Model
is automatically nested
in a @OA\Schema
.
The media type defaults to application/json
.
To use @Model
directly within a @OA\Schema
, @OA\Items
or @OA\Property
, you have to use the $ref
field:
1 2 3 4 5 6 7 8 9 10 11 12 13
/**
* @OA\Response(
* @OA\JsonContent(ref=@Model(type=User::class))
* )
*
* or
*
* @OA\Response(@OA\XmlContent(
* @OA\Schema(type="object",
* @OA\Property(property="foo", ref=@Model(type=FooClass::class))
* )
* ))
*/
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#[OA\Response(
content: new OA\JsonContent(ref: new Model(type: User::class))
)]
/**
* or
*/
#[OA\Response(
content: new OA\XmlContent(example: new OA\Schema(
type: 'object',
properties: [
new OA\Property(property: 'foo', ref: new Model(type: FooClass::class))
]
))
)]
Symfony Form types
You can customize the documentation of a form field using the documentation
option:
1 2 3 4 5 6
$builder->add('username', TextType::class, [
'documentation' => [
'type' => 'string', // would have been automatically detected in this case
'description' => 'Your username.',
],
]);
See the OpenAPI 3.0 specification to see all the available fields of the documentation
option (it accepts the same fields as the OpenApi Property
object).
General PHP objects
Tip
If you're not using the JMS Serializer, the Symfony PropertyInfo component is used to describe your models. It supports doctrine annotations, type hints, and even PHP doc blocks. It does also support serialization groups when using the Symfony serializer.
If you're using the JMS Serializer, the metadata of the JMS serializer are used by default to describe your models. Additional information is extracted from the PHP doc block comment, but the property types must be specified in the JMS annotations.
NOTE: If you are using serialization contexts (e.g. Groups) each permutation will be treated as a separate Path. For example if you have the following two variations defined in different places in your code:
1 2 3 4 5 6 7 8 9 10 11 12 13
/**
* A nested serializer property with no context group
*
* @JMS\VirtualProperty
* @JMS\Type("ArrayCollection<App\Response\ItemResponse>")
* @JMS\Since("1.0")
*
* @return Collection|ItemResponse[]
*/
public function getItems(): Collection|array
{
return $this->items;
}
1
@OA\Schema(ref=@Model(type="App\Response\ItemResponse", groups=["Default"])),
1
#[OA\Schema(ref: new Model(type: App\Response\ItemResponse::class, groups: ['Default']))]
It will generate two different component schemas (ItemResponse, ItemResponse2), even though Default and blank are the same. This is by design.
In case you prefer using the Symfony PropertyInfo component (you won't be able to use JMS serialization groups), you can disable JMS serializer support in your config:
1 2
nelmio_api_doc:
models: { use_jms: false }
Alternatively, it is also possible to opt out of JMS serializer usage per endpoint by setting useJms
in the serializationContext:
1
/** @OA\Response(response=200, @Model(type=UserDto::class, serializationContext={"useJms"=false})) */
1
#[OA\Response(response: 200, content: new Model(type: UserDto::class, serializationContext: ["useJms" => false]))]
When using the JMS serializer combined with willdurand/Hateoas (and the BazingaHateoasBundle), HATEOAS metadata are automatically extracted
If you want to customize the documentation of an object's property, you can use @OA\Property
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;
class User
{
/**
* @var int
* @OA\Property(description="The unique identifier of the user.")
*/
public $id;
/**
* @OA\Property(type="string", maxLength=255)
*/
public $username;
/**
* @OA\Property(ref=@Model(type=User::class))
*/
public $friend;
/**
* @OA\Property(description="This is my coworker!")
*/
public setCoworker(User $coworker) {
// ...
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Attributes as OA;
class User
{
/**
* @var int
*/
#[OA\Property(description: 'The unique identifier of the user.')]
public $id;
#[OA\Property(type: 'string', maxLength: 255)]
public $username;
#[OA\Property(ref: new Model(type: User::class))]
public $friend;
#[OA\Property(description: 'This is my coworker!')]
public setCoworker(User $coworker) {
// ...
}
}
See the OpenAPI 3.0 specification to see all the available fields of @OA\Property
.
Learn more
If you need more complex features, take a look at: