Christopher Davis 235963df41
Merge pull request #1902 from chrisguitarguy/constraint_groups
Respect Constraint Validation Groups When Describing Models
2022-06-10 16:15:42 -05:00

583 lines
19 KiB
ReStructuredText
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, PHP annotations, `Swagger-Php`_ annotations,
`FOSRestBundle`_ annotations and applications using `Api-Platform`_.
.. _`Swagger-Php`: https://github.com/zircote/swagger-php
.. _`FOSRestBundle`: https://github.com/FriendsOfSymfony/FOSRestBundle
.. _`Api-Platform`: https://github.com/api-platform/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.
Migrate from 3.x to 4.0
-----------------------
`To migrate from 3.x to 4.0, follow our guide.`__
__ https://github.com/nelmio/NelmioApiDocBundle/blob/master/UPGRADE-4.0.md
Installation
------------
Open a command console, enter your project directory and execute the following command to download the latest version of this bundle:
.. code-block:: bash
$ 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::
class AppKernel extends Kernel
{
public function registerBundles(): iterable
{
$bundles = [
// ...
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
];
// ...
}
}
To browse your documentation with Swagger UI, register the following route:
.. code-block:: yaml
# config/routes.yaml
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
If you also want to expose it in JSON, register this route:
.. code-block:: yaml
# 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:
.. code-block:: yaml
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps (document only routes under /api, expect /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).
.. code-block:: yaml
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
components:
securitySchemes:
Bearer:
type: http
scheme: bearer
bearerFormat: JWT
security:
- Bearer: []
.. _`the OpenAPI 3.0 specification (formerly Swagger)`: https://swagger.io/docs/specification
.. 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::
.. configuration-block::
.. code-block:: php-annotations
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)
{
// ...
}
}
.. code-block:: php-attributes
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.
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::
.. configuration-block::
.. code-block:: php-annotations
/**
* @OA\Response(
* response=200,
*     @Model(type=User::class)
* )
*/
.. code-block:: php-attributes
#[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::
.. configuration-block::
.. code-block:: php-annotations
/**
* @OA\Response(
* response=200,
*     @Model(type=User::class, groups={"non_sensitive_data"})
* )
*/
.. code-block:: php-attributes
#[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::
.. code-block:: yaml
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:
.. configuration-block::
.. code-block:: php-annotations
use Symfony\Component\Serializer\Annotation\Group;
use Symfony\Component\Validator\Constraints as Assert;
class UserDto
{
/**
* @Group({"default", "create", "update"})
* @Assert\NotBlank(groups={"default", "create"})
*/
public string $username;
}
.. code-block:: php-attributes
use Symfony\Component\Serializer\Annotation\Group;
use Symfony\Component\Validator\Constraints as Assert;
class UserDto
{
#[Group(["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.
.. configuration-block::
.. code-block:: php-annotations
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"}))
*/
.. code-block:: php-attributes
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::
.. configuration-block::
.. code-block:: php-annotations
/**
* @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))
* )
* ))
*/
.. code-block:: php-attributes
#[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::
$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).
__ https://swagger.io/specification/
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:
.. code-block:: php
/**
* 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;
}
.. configuration-block::
.. code-block:: php-annotations
@OA\Schema(ref=@Model(type="App\Response\ItemResponse", groups=["Default"])),
.. code-block:: php-attributes
#[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:
.. code-block:: yaml
nelmio_api_doc:
models: { use_jms: 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``::
.. configuration-block::
.. code-block:: php-annotations
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) {
// ...
}
}
.. code-block:: php-attributes
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``.
__ https://swagger.io/specification/
Learn more
----------
If you need more complex features, take a look at:
.. toctree::
:maxdepth: 1
areas
alternative_names
customization
commands
faq
security
.. _`SwaggerPHP examples`: https://github.com/zircote/swagger-php/tree/master/Examples
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
.. _`willdurand/Hateoas`: https://github.com/willdurand/Hateoas
.. _`BazingaHateoasBundle`: https://github.com/willdurand/BazingaHateoasBundle
.. _`JMS serializer`: https://jmsyst.com/libs/serializer
.. _`Symfony form`: https://symfony.com/doc/current/forms.html
.. _`Symfony serializer`: https://symfony.com/doc/current/components/serializer.html