mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-02 15:51:48 +03:00
Merge remote-tracking branch 'origin/master' into constraint_groups
This commit is contained in:
commit
6b2ef45b24
@ -100,7 +100,11 @@ final class ModelRegistry
|
||||
}
|
||||
|
||||
if (null === $schema) {
|
||||
throw new \LogicException(sprintf('Schema of type "%s" can\'t be generated, no describer supports it.', $this->typeToString($model->getType())));
|
||||
$errorMessage = sprintf('Schema of type "%s" can\'t be generated, no describer supports it.', $this->typeToString($model->getType()));
|
||||
if (Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() && !class_exists($className = $model->getType()->getClassName())) {
|
||||
$errorMessage .= sprintf(' Class "\\%s" does not exist, did you forget a use statement, or typed it wrong?', $className);
|
||||
}
|
||||
throw new \LogicException($errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,7 +178,7 @@ final class ModelRegistry
|
||||
private function typeToString(Type $type): string
|
||||
{
|
||||
if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()) {
|
||||
return $type->getClassName();
|
||||
return '\\'.$type->getClassName();
|
||||
} elseif ($type->isCollection()) {
|
||||
if (null !== $collectionType = $this->getCollectionValueType($type)) {
|
||||
return $this->typeToString($collectionType).'[]';
|
||||
|
@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber\Annotations;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||
use OpenApi\Annotations as OA;
|
||||
use OpenApi\Generator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -44,10 +45,14 @@ class AnnotationsReader
|
||||
);
|
||||
}
|
||||
|
||||
public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): void
|
||||
public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): UpdateClassDefinitionResult
|
||||
{
|
||||
$this->openApiAnnotationsReader->updateSchema($reflectionClass, $schema);
|
||||
$this->symfonyConstraintAnnotationReader->setSchema($schema);
|
||||
|
||||
return new UpdateClassDefinitionResult(
|
||||
$this->shouldDescribeModelProperties($schema)
|
||||
);
|
||||
}
|
||||
|
||||
public function getPropertyName($reflection, string $default): string
|
||||
@ -61,4 +66,15 @@ class AnnotationsReader
|
||||
$this->phpDocReader->updateProperty($reflection, $property);
|
||||
$this->symfonyConstraintAnnotationReader->updateProperty($reflection, $property, $serializationGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* if an objects schema type and ref are undefined OR the object was manually
|
||||
* defined as an object, then we're good to do the normal describe flow of
|
||||
* class properties.
|
||||
*/
|
||||
private function shouldDescribeModelProperties(OA\Schema $schema): bool
|
||||
{
|
||||
return (Generator::UNDEFINED === $schema->type || 'object' === $schema->type)
|
||||
&& Generator::UNDEFINED === $schema->ref;
|
||||
}
|
||||
}
|
||||
|
41
ModelDescriber/Annotations/UpdateClassDefinitionResult.php
Normal file
41
ModelDescriber/Annotations/UpdateClassDefinitionResult.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\ModelDescriber\Annotations;
|
||||
|
||||
/**
|
||||
* result object returned from `AnnotationReader::updateDefinition` as a way
|
||||
* to pass back information about manually defined schema elements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UpdateClassDefinitionResult
|
||||
{
|
||||
/**
|
||||
* Whether or not the model describer shoudl continue reading class properties
|
||||
* after updating the open api schema from an `OA\Schema` definition.
|
||||
*
|
||||
* Users may maually define a `type` or `ref` on a schema, and if that's the case
|
||||
* model describers should _probably_ not describe any additional properties or try
|
||||
* to merge in properties.
|
||||
*/
|
||||
private $shouldDescribeModelProperties;
|
||||
|
||||
public function __construct(bool $shouldDescribeModelProperties)
|
||||
{
|
||||
$this->shouldDescribeModelProperties = $shouldDescribeModelProperties;
|
||||
}
|
||||
|
||||
public function shouldDescribeModelProperties(): bool
|
||||
{
|
||||
return $this->shouldDescribeModelProperties;
|
||||
}
|
||||
}
|
34
ModelDescriber/EnumModelDescriber.php
Normal file
34
ModelDescriber/EnumModelDescriber.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\ModelDescriber;
|
||||
|
||||
use Nelmio\ApiDocBundle\Model\Model;
|
||||
use OpenApi\Annotations\Schema;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
class EnumModelDescriber implements ModelDescriberInterface
|
||||
{
|
||||
public function describe(Model $model, Schema $schema)
|
||||
{
|
||||
$enumClass = $model->getType()->getClassName();
|
||||
|
||||
$enums = [];
|
||||
foreach ($enumClass::cases() as $enumCase) {
|
||||
$enums[] = $enumCase->value;
|
||||
}
|
||||
|
||||
$schema->type = is_subclass_of($enumClass, \IntBackedEnum::class) ? 'int' : 'string';
|
||||
$schema->enum = $enums;
|
||||
}
|
||||
|
||||
public function supports(Model $model): bool
|
||||
{
|
||||
if (!function_exists('enum_exists')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType()
|
||||
&& enum_exists($model->getType()->getClassName())
|
||||
&& is_subclass_of($model->getType()->getClassName(), \BackedEnum::class);
|
||||
}
|
||||
}
|
@ -69,8 +69,6 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
|
||||
throw new \LogicException('You need to enable forms in your application to use a form as a model.');
|
||||
}
|
||||
|
||||
$schema->type = 'object';
|
||||
|
||||
$class = $model->getType()->getClassName();
|
||||
|
||||
$annotationsReader = new AnnotationsReader(
|
||||
@ -79,7 +77,13 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
|
||||
$this->mediaTypes,
|
||||
$this->useValidationGroups
|
||||
);
|
||||
$annotationsReader->updateDefinition(new \ReflectionClass($class), $schema);
|
||||
$classResult = $annotationsReader->updateDefinition(new \ReflectionClass($class), $schema);
|
||||
|
||||
if (!$classResult->shouldDescribeModelProperties()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema->type = 'object';
|
||||
|
||||
$form = $this->formFactory->create($class, null, $model->getOptions() ?? []);
|
||||
$this->parseForm($schema, $form);
|
||||
|
@ -80,14 +80,18 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
||||
throw new \InvalidArgumentException(sprintf('No metadata found for class %s.', $className));
|
||||
}
|
||||
|
||||
$schema->type = 'object';
|
||||
$annotationsReader = new AnnotationsReader(
|
||||
$this->doctrineReader,
|
||||
$this->modelRegistry,
|
||||
$this->mediaTypes,
|
||||
$this->useValidationGroups
|
||||
);
|
||||
$annotationsReader->updateDefinition(new \ReflectionClass($className), $schema);
|
||||
$classResult = $annotationsReader->updateDefinition(new \ReflectionClass($className), $schema);
|
||||
|
||||
if (!$classResult->shouldDescribeModelProperties()) {
|
||||
return;
|
||||
}
|
||||
$schema->type = 'object';
|
||||
|
||||
$isJmsV1 = null !== $this->namingStrategy;
|
||||
|
||||
|
@ -62,8 +62,6 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
|
||||
|
||||
public function describe(Model $model, OA\Schema $schema)
|
||||
{
|
||||
$schema->type = 'object';
|
||||
|
||||
$class = $model->getType()->getClassName();
|
||||
$schema->_context->class = $class;
|
||||
|
||||
@ -79,7 +77,13 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
|
||||
$this->mediaTypes,
|
||||
$this->useValidationGroups
|
||||
);
|
||||
$annotationsReader->updateDefinition($reflClass, $schema);
|
||||
$classResult = $annotationsReader->updateDefinition($reflClass, $schema);
|
||||
|
||||
if (!$classResult->shouldDescribeModelProperties()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema->type = 'object';
|
||||
|
||||
$discriminatorMap = $this->getAnnotation($reflClass, DiscriminatorMap::class);
|
||||
if ($discriminatorMap && Generator::UNDEFINED === $schema->discriminator) {
|
||||
|
@ -80,6 +80,10 @@
|
||||
<tag name="nelmio_api_doc.model_describer" />
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.model_describers.enum" class="Nelmio\ApiDocBundle\ModelDescriber\EnumModelDescriber" public="false">
|
||||
<tag name="nelmio_api_doc.model_describer" priority="100"/>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.model_describers.object_fallback" class="Nelmio\ApiDocBundle\ModelDescriber\FallbackObjectModelDescriber" public="false">
|
||||
<tag name="nelmio_api_doc.model_describer" priority="-1000" />
|
||||
</service>
|
||||
|
@ -32,6 +32,7 @@ You can define areas which will each generates a different documentation:
|
||||
store:
|
||||
# Includes routes with names containing 'store'
|
||||
name_patterns: [ store ]
|
||||
|
||||
|
||||
Your main documentation is under the ``default`` area. It's the one shown when accessing ``/api/doc``.
|
||||
|
||||
@ -52,3 +53,38 @@ Then update your routing to be able to access your different documentations:
|
||||
# defaults: { _controller: nelmio_api_doc.controller.swagger }
|
||||
|
||||
That's all! You can now access ``/api/doc/internal``, ``/api/doc/commercial`` and ``/api/doc/store``.
|
||||
|
||||
Use annotations to filter documented routes in each area
|
||||
--------------------------------------------------------
|
||||
|
||||
You can use the `@Areas` annotation inside your controllers to define your routes' areas.
|
||||
|
||||
First, you need to define which areas will use the`@Areas` annotations to filter
|
||||
the routes that should be documented:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nelmio_api_doc:
|
||||
areas:
|
||||
default:
|
||||
path_patterns: [ ^/api ]
|
||||
internal:
|
||||
with_annotations: true
|
||||
|
||||
Then add the annotation before your controller or action::
|
||||
|
||||
use Nelmio\Annotations as Nelmio;
|
||||
|
||||
/**
|
||||
* @Nelmio\Areas({"internal"}) => All actions in this controller are documented under the 'internal' area
|
||||
*/
|
||||
class MyController
|
||||
{
|
||||
/**
|
||||
* @Nelmio\Areas({"internal"}) => This action is documented under the 'internal' area
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ A command is provided in order to dump the documentation in ``json``, ``yaml`` o
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump [--format="..."]
|
||||
$ php app/console nelmio:apidoc:dump [--format="..."]
|
||||
|
||||
The ``--format`` option allows to choose the format (default is: ``json``).
|
||||
|
||||
@ -14,20 +14,20 @@ without whitespace, use the ``--no-pretty`` option.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump --format=json > json-pretty-formatted.json
|
||||
$ php app/console api:doc:dump --format=json --no-pretty > json-no-pretty.json
|
||||
$ php app/console nelmio:apidoc:dump --format=json > json-pretty-formatted.json
|
||||
$ php app/console nelmio:apidoc:dump --format=json --no-pretty > json-no-pretty.json
|
||||
|
||||
Every format can override API url. Useful if static documentation is not hosted on API url:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump --format=yaml --server-url "http://example.com/api" > api.yaml
|
||||
$ php app/console nelmio:apidoc:dump --format=yaml --server-url "http://example.com/api" > api.yaml
|
||||
|
||||
For example to generate a static version of your documentation you can use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump --format=html > api.html
|
||||
$ php app/console nelmio:apidoc:dump --format=html > api.html
|
||||
|
||||
By default, the generated HTML will add the sandbox feature.
|
||||
If you want to generate a static version of your documentation without sandbox,
|
||||
@ -40,6 +40,6 @@ or configure UI configuration, use the ``--html-config`` option.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump --format=html --html-config '{"assets_mode":"offline","server_url":"https://example.com","swagger_ui_config":{"supportedSubmitMethods":[]}}' > api.html
|
||||
$ php app/console nelmio:apidoc:dump --format=html --html-config '{"assets_mode":"offline","server_url":"https://example.com","swagger_ui_config":{"supportedSubmitMethods":[]}}' > api.html
|
||||
|
||||
.. _`configure Swagger UI`: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
|
||||
|
@ -210,3 +210,30 @@ A: Use ``disable_default_routes`` config in your area.
|
||||
areas:
|
||||
default:
|
||||
disable_default_routes: true
|
||||
|
||||
Overriding a Form or Plain PHP Object Schema Type
|
||||
-------------------------------------------------
|
||||
|
||||
Q: I'd like to define a PHP object or form with a type other any ``object``, how
|
||||
do I do that?
|
||||
|
||||
A: By using the ``@OA\Schema`` annotation or attribute with a ``type`` or ``ref``.
|
||||
Note, however, that a ``type="object"`` will still read all a models properties.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use OpenApi\Annotations as OA;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
|
||||
/**
|
||||
* @OA\Schema(type="array", @OA\Items(ref=@Model(type=SomeEntity::class)))
|
||||
*
|
||||
* or define a `ref`:
|
||||
* @OA\Schema(ref="#/components/schemas/SomeRef")
|
||||
*/
|
||||
class SomeCollection implements \IteratorAggregate
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ This bundle supports *Symfony* route requirements, PHP annotations, `Swagger-Php
|
||||
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
|
||||
-----------------------
|
||||
|
||||
@ -99,7 +101,7 @@ 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):
|
||||
`the OpenAPI 3.0 specification (formerly Swagger)`_ to know the available fields).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -127,53 +129,110 @@ You can configure global information in the bundle configuration ``documentation
|
||||
|
||||
.. note::
|
||||
|
||||
If you're using Flex, this config is there by default. Don't forget to adapt it to your app!
|
||||
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::
|
||||
|
||||
namespace AppBundle\Controller;
|
||||
.. configuration-block::
|
||||
|
||||
use AppBundle\Entity\User;
|
||||
use AppBundle\Entity\Reward;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Annotation\Security;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
.. code-block:: php-annotations
|
||||
|
||||
namespace AppBundle\Controller;
|
||||
|
||||
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)
|
||||
use AppBundle\Entity\User;
|
||||
use AppBundle\Entity\Reward;
|
||||
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\User;
|
||||
use AppBundle\Entity\Reward;
|
||||
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
|
||||
----------
|
||||
|
||||
@ -188,23 +247,48 @@ This annotation has two options:
|
||||
|
||||
* ``type`` to specify your model's type::
|
||||
|
||||
/**
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* @Model(type=User::class)
|
||||
* )
|
||||
*/
|
||||
.. 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::
|
||||
|
||||
/**
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* @Model(type=User::class, groups={"non_sensitive_data"})
|
||||
* )
|
||||
*/
|
||||
|
||||
.. tip::
|
||||
.. 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'])
|
||||
)]
|
||||
|
||||
.. tip::
|
||||
|
||||
When used at the root of ``@OA\Response`` and ``@OA\Parameter``, ``@Model`` is automatically nested
|
||||
in a ``@OA\Schema``.
|
||||
@ -213,6 +297,10 @@ This annotation has two options:
|
||||
|
||||
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))
|
||||
@ -227,6 +315,23 @@ This annotation has two options:
|
||||
* ))
|
||||
*/
|
||||
|
||||
.. 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
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -277,7 +382,15 @@ General PHP objects
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@OA\Schema(ref=@Model(type="App\Response\ItemResponse", groups=["Default"])),
|
||||
.. 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.
|
||||
|
||||
@ -295,34 +408,64 @@ General PHP objects
|
||||
|
||||
If you want to customize the documentation of an object's property, you can use ``@OA\Property``::
|
||||
|
||||
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;
|
||||
.. configuration-block::
|
||||
|
||||
/**
|
||||
* @OA\Property(type="string", maxLength=255)
|
||||
*/
|
||||
public $username;
|
||||
.. code-block:: php-annotations
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use OpenApi\Annotations as OA;
|
||||
|
||||
/**
|
||||
* @OA\Property(ref=@Model(type=User::class))
|
||||
*/
|
||||
public $friend;
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @OA\Property(description="The unique identifier of the user.")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @OA\Property(description="This is my coworker!")
|
||||
*/
|
||||
public setCoworker(User $coworker) {
|
||||
// ...
|
||||
/**
|
||||
* @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``.
|
||||
|
||||
@ -343,6 +486,7 @@ If you need more complex features, take a look at:
|
||||
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
|
||||
|
@ -17,11 +17,16 @@ use Nelmio\ApiDocBundle\Annotation\Operation;
|
||||
use Nelmio\ApiDocBundle\Annotation\Security;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\CompoundEntity;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithAlternateType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithAlternateSchemaType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithRefType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@ -272,4 +277,68 @@ class ApiController80
|
||||
public function symfonyConstraintsWithGroupsAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/alternate-entity-type", methods={"GET", "POST"})
|
||||
*
|
||||
* @OA\Get(operationId="alternate-entity-type")
|
||||
* @OA\Response(response=200, description="success", @OA\JsonContent(
|
||||
* ref=@Model(type=EntityWithAlternateType::class),
|
||||
* ))
|
||||
*/
|
||||
public function alternateEntityType()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/entity-with-ref", methods={"GET", "POST"})
|
||||
*
|
||||
* @OA\Get(operationId="entity-with-ref")
|
||||
* @OA\Response(response=200, description="success", @OA\JsonContent(
|
||||
* ref=@Model(type=EntityWithRef::class),
|
||||
* ))
|
||||
*/
|
||||
public function entityWithRef()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/entity-with-object-type", methods={"GET", "POST"})
|
||||
*
|
||||
* @OA\Get(operationId="entity-with-object-type")
|
||||
* @OA\Response(response=200, description="success", @OA\JsonContent(
|
||||
* ref=@Model(type=EntityWithObjectType::class),
|
||||
* ))
|
||||
*/
|
||||
public function entityWithObjectType()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/form-with-alternate-type", methods={"POST"})
|
||||
* @OA\Response(
|
||||
* response="204",
|
||||
* description="Operation automatically detected",
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* @Model(type=FormWithAlternateSchemaType::class))
|
||||
* )
|
||||
*/
|
||||
public function formWithAlternateSchemaType()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/form-with-ref-type", methods={"POST"})
|
||||
* @OA\Response(
|
||||
* response="204",
|
||||
* description="Operation automatically detected",
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* @Model(type=FormWithRefType::class))
|
||||
* )
|
||||
*/
|
||||
public function formWithRefSchemaType()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use Nelmio\ApiDocBundle\Annotation\Areas;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Annotation\Security;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article81;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
@ -65,4 +66,10 @@ class ApiController81 extends ApiController80
|
||||
#[OA\PathParameter] string $product_id
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/enum')]
|
||||
#[OA\Response(response: '201', description: '', attachables: [new Model(type: Article81::class)])]
|
||||
public function enum()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
12
Tests/Functional/Entity/Article81.php
Normal file
12
Tests/Functional/Entity/Article81.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;
|
||||
|
||||
class Article81
|
||||
{
|
||||
public function __construct(
|
||||
public readonly int $id,
|
||||
public readonly ArticleType81 $type,
|
||||
) {
|
||||
}
|
||||
}
|
9
Tests/Functional/Entity/ArticleType81.php
Normal file
9
Tests/Functional/Entity/ArticleType81.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;
|
||||
|
||||
enum ArticleType81: string
|
||||
{
|
||||
case DRAFT = 'draft';
|
||||
case FINAL = 'final';
|
||||
}
|
35
Tests/Functional/Entity/EntityWithAlternateType.php
Normal file
35
Tests/Functional/Entity/EntityWithAlternateType.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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 ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use OpenApi\Annotations as OA;
|
||||
|
||||
/**
|
||||
* @OA\Schema(type="array", @OA\Items(type="string"))
|
||||
*/
|
||||
class EntityWithAlternateType implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $ignored = 'this property should be ignored because of the annotation above';
|
||||
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator([
|
||||
'abc',
|
||||
'def',
|
||||
]);
|
||||
}
|
||||
}
|
25
Tests/Functional/Entity/EntityWithObjectType.php
Normal file
25
Tests/Functional/Entity/EntityWithObjectType.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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 OpenApi\Annotations as OA;
|
||||
|
||||
/**
|
||||
* @OA\Schema(type="object")
|
||||
*/
|
||||
class EntityWithObjectType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $notIgnored = 'this should be read';
|
||||
}
|
25
Tests/Functional/Entity/EntityWithRef.php
Normal file
25
Tests/Functional/Entity/EntityWithRef.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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 OpenApi\Annotations as OA;
|
||||
|
||||
/**
|
||||
* @OA\Schema(ref="#/components/schemas/Test")
|
||||
*/
|
||||
class EntityWithRef
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $ignored = 'this property should be ignored because of the annotation above';
|
||||
}
|
31
Tests/Functional/Form/FormWithAlternateSchemaType.php
Normal file
31
Tests/Functional/Form/FormWithAlternateSchemaType.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Form;
|
||||
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\InputType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @OA\Schema(type="string")
|
||||
*/
|
||||
class FormWithAlternateSchemaType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('ignored', InputType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
31
Tests/Functional/Form/FormWithRefType.php
Normal file
31
Tests/Functional/Form/FormWithRefType.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Form;
|
||||
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\InputType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @OA\Schema(ref="#/components/schemas/Test")
|
||||
*/
|
||||
class FormWithRefType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('ignored', InputType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
@ -624,4 +624,60 @@ class FunctionalTest extends WebTestCase
|
||||
|
||||
$this->assertFalse($model->additionalProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP >= 8.1
|
||||
*/
|
||||
public function testEnumSupport()
|
||||
{
|
||||
$model = $this->getModel('ArticleType81');
|
||||
|
||||
$this->assertSame('string', $model->type);
|
||||
$this->assertCount(2, $model->enum);
|
||||
}
|
||||
|
||||
public function testEntitiesWithOverriddenSchemaTypeDoNotReadOtherProperties()
|
||||
{
|
||||
$model = $this->getModel('EntityWithAlternateType');
|
||||
|
||||
$this->assertSame('array', $model->type);
|
||||
$this->assertSame('string', $model->items->type);
|
||||
$this->assertSame(Generator::UNDEFINED, $model->properties);
|
||||
}
|
||||
|
||||
public function testEntitiesWithRefInSchemaDoNoReadOtherProperties()
|
||||
{
|
||||
$model = $this->getModel('EntityWithRef');
|
||||
|
||||
$this->assertSame(Generator::UNDEFINED, $model->type);
|
||||
$this->assertSame('#/components/schemas/Test', $model->ref);
|
||||
$this->assertSame(Generator::UNDEFINED, $model->properties);
|
||||
}
|
||||
|
||||
public function testEntitiesWithObjectTypeStillReadProperties()
|
||||
{
|
||||
$model = $this->getModel('EntityWithObjectType');
|
||||
|
||||
$this->assertSame('object', $model->type);
|
||||
$this->assertCount(1, $model->properties);
|
||||
$property = Util::getProperty($model, 'notIgnored');
|
||||
$this->assertSame('string', $property->type);
|
||||
}
|
||||
|
||||
public function testFormsWithOverriddenSchemaTypeDoNotReadOtherProperties()
|
||||
{
|
||||
$model = $this->getModel('FormWithAlternateSchemaType');
|
||||
|
||||
$this->assertSame('string', $model->type);
|
||||
$this->assertSame(Generator::UNDEFINED, $model->properties);
|
||||
}
|
||||
|
||||
public function testFormWithRefInSchemaDoNoReadOtherProperties()
|
||||
{
|
||||
$model = $this->getModel('FormWithRefType');
|
||||
|
||||
$this->assertSame(Generator::UNDEFINED, $model->type);
|
||||
$this->assertSame('#/components/schemas/Test', $model->ref);
|
||||
$this->assertSame(Generator::UNDEFINED, $model->properties);
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +234,20 @@ class ModelRegistryTest extends TestCase
|
||||
{
|
||||
return [
|
||||
[new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true), 'mixed[]'],
|
||||
[new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class), self::class],
|
||||
[new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class), '\\'.self::class],
|
||||
];
|
||||
}
|
||||
|
||||
public function testUnsupportedTypeExceptionWithNonExistentClass()
|
||||
{
|
||||
$className = DoesNotExist::class;
|
||||
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, $className);
|
||||
|
||||
$this->expectException('\LogicException');
|
||||
$this->expectExceptionMessage(sprintf('Schema of type "\%s" can\'t be generated, no describer supports it. Class "\Nelmio\ApiDocBundle\Tests\Model\DoesNotExist" does not exist, did you forget a use statement, or typed it wrong?', $className));
|
||||
|
||||
$registry = new ModelRegistry([], new OA\OpenApi([]));
|
||||
$registry->register(new Model($type));
|
||||
$registry->registerSchemas();
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@
|
||||
"symfony/twig-bundle": "^4.4|^5.2|^6.0",
|
||||
"symfony/validator": "^4.4|^5.2|^6.0",
|
||||
|
||||
"api-platform/core": "^2.4",
|
||||
"api-platform/core": "^2.6.8",
|
||||
"friendsofsymfony/rest-bundle": "^2.8|^3.0",
|
||||
"willdurand/hateoas-bundle": "^1.0|^2.0",
|
||||
"jms/serializer-bundle": "^2.3|^3.0|^4.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user