mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-03-11 18:16:13 +03:00
Merge remote-tracking branch 'origin/master' into constraint_groups
This commit is contained in:
commit
fca94057d2
11
.github/workflows/continuous-integration.yml
vendored
11
.github/workflows/continuous-integration.yml
vendored
@ -34,6 +34,9 @@ jobs:
|
|||||||
symfony-require: "5.4.*"
|
symfony-require: "5.4.*"
|
||||||
- php-version: 8.1
|
- php-version: 8.1
|
||||||
symfony-require: "5.4.*"
|
symfony-require: "5.4.*"
|
||||||
|
- php-version: 8.1
|
||||||
|
symfony-require: "6.0.*"
|
||||||
|
api-platform: "early"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
@ -58,6 +61,14 @@ jobs:
|
|||||||
path: ${{ steps.composercache.outputs.dir }}
|
path: ${{ steps.composercache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||||
restore-keys: ${{ runner.os }}-composer-
|
restore-keys: ${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: "Use an early version of Api-platform with symfony 6 support"
|
||||||
|
if: ${{ matrix.api-platform == 'early' }}
|
||||||
|
env:
|
||||||
|
SYMFONY_REQUIRE: "${{ matrix.symfony-require }}"
|
||||||
|
run: |
|
||||||
|
composer config repositories.api-platform git https://github.com/PierreRebeilleau/core.git
|
||||||
|
composer require api-platform/core:dev-test-compatibility --no-update --dev
|
||||||
|
|
||||||
- name: "Install dependencies with composer"
|
- name: "Install dependencies with composer"
|
||||||
env:
|
env:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@
|
|||||||
/.phpunit.result.cache
|
/.phpunit.result.cache
|
||||||
/Tests/Functional/cache
|
/Tests/Functional/cache
|
||||||
/Tests/Functional/logs
|
/Tests/Functional/logs
|
||||||
|
.idea
|
||||||
|
@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Annotation;
|
|||||||
/**
|
/**
|
||||||
* @Annotation
|
* @Annotation
|
||||||
*/
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||||
final class Areas
|
final class Areas
|
||||||
{
|
{
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
@ -22,6 +23,10 @@ final class Areas
|
|||||||
public function __construct(array $properties)
|
public function __construct(array $properties)
|
||||||
{
|
{
|
||||||
if (!array_key_exists('value', $properties) || !is_array($properties['value'])) {
|
if (!array_key_exists('value', $properties) || !is_array($properties['value'])) {
|
||||||
|
$properties['value'] = array_values($properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([] === $properties['value']) {
|
||||||
throw new \InvalidArgumentException('An array of areas was expected');
|
throw new \InvalidArgumentException('An array of areas was expected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,12 @@ namespace Nelmio\ApiDocBundle\Annotation;
|
|||||||
|
|
||||||
use OpenApi\Annotations\AbstractAnnotation;
|
use OpenApi\Annotations\AbstractAnnotation;
|
||||||
use OpenApi\Annotations\Parameter;
|
use OpenApi\Annotations\Parameter;
|
||||||
|
use OpenApi\Generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Annotation
|
* @Annotation
|
||||||
*/
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||||
final class Model extends AbstractAnnotation
|
final class Model extends AbstractAnnotation
|
||||||
{
|
{
|
||||||
/** {@inheritdoc} */
|
/** {@inheritdoc} */
|
||||||
@ -46,4 +48,22 @@ final class Model extends AbstractAnnotation
|
|||||||
* @var mixed[]
|
* @var mixed[]
|
||||||
*/
|
*/
|
||||||
public $options;
|
public $options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $properties
|
||||||
|
* @param string[] $groups
|
||||||
|
* @param mixed[] $options
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
array $properties = [],
|
||||||
|
string $type = Generator::UNDEFINED,
|
||||||
|
array $groups = null,
|
||||||
|
array $options = null
|
||||||
|
) {
|
||||||
|
parent::__construct($properties + [
|
||||||
|
'type' => $type,
|
||||||
|
'groups' => $groups,
|
||||||
|
'options' => $options,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ use OpenApi\Annotations\Operation as BaseOperation;
|
|||||||
/**
|
/**
|
||||||
* @Annotation
|
* @Annotation
|
||||||
*/
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||||
class Operation extends BaseOperation
|
class Operation extends BaseOperation
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ use OpenApi\Annotations\AbstractAnnotation;
|
|||||||
/**
|
/**
|
||||||
* @Annotation
|
* @Annotation
|
||||||
*/
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||||
class Security extends AbstractAnnotation
|
class Security extends AbstractAnnotation
|
||||||
{
|
{
|
||||||
/** {@inheritdoc} */
|
/** {@inheritdoc} */
|
||||||
@ -35,4 +36,15 @@ class Security extends AbstractAnnotation
|
|||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public $scopes = [];
|
public $scopes = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
array $properties = [],
|
||||||
|
string $name = null,
|
||||||
|
array $scopes = []
|
||||||
|
) {
|
||||||
|
parent::__construct($properties + [
|
||||||
|
'name' => $name,
|
||||||
|
'scopes' => $scopes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ use Nelmio\ApiDocBundle\Describer\DescriberInterface;
|
|||||||
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
|
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
|
||||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||||
use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
|
use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
|
||||||
use Nelmio\ApiDocBundle\OpenApiPhp\DefaultOperationId;
|
|
||||||
use Nelmio\ApiDocBundle\OpenApiPhp\ModelRegister;
|
use Nelmio\ApiDocBundle\OpenApiPhp\ModelRegister;
|
||||||
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
|
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
|
||||||
use OpenApi\Analysis;
|
use OpenApi\Analysis;
|
||||||
@ -84,6 +83,14 @@ final class ApiDocGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$generator = new Generator();
|
||||||
|
// Remove OperationId processor as we use a lot of generated annotations which do not have enough information in their context
|
||||||
|
// to generate these ids properly.
|
||||||
|
// @see https://github.com/zircote/swagger-php/issues/1153
|
||||||
|
$generator->setProcessors(array_filter($generator->getProcessors(), function ($processor) {
|
||||||
|
return !$processor instanceof \OpenApi\Processors\OperationId;
|
||||||
|
}));
|
||||||
|
|
||||||
$this->openApi = new OpenApi([]);
|
$this->openApi = new OpenApi([]);
|
||||||
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->openApi, $this->alternativeNames);
|
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->openApi, $this->alternativeNames);
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
@ -97,7 +104,12 @@ final class ApiDocGenerator
|
|||||||
$describer->describe($this->openApi);
|
$describer->describe($this->openApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context = Util::createContext();
|
$context = Util::createContext(
|
||||||
|
// BC for for zircote/swagger-php < 4.2
|
||||||
|
method_exists($generator, 'getVersion')
|
||||||
|
? ['version' => $generator->getVersion()]
|
||||||
|
: []
|
||||||
|
);
|
||||||
$analysis = new Analysis([], $context);
|
$analysis = new Analysis([], $context);
|
||||||
$analysis->addAnnotation($this->openApi, $context);
|
$analysis->addAnnotation($this->openApi, $context);
|
||||||
|
|
||||||
@ -108,10 +120,7 @@ final class ApiDocGenerator
|
|||||||
// Calculate the associated schemas
|
// Calculate the associated schemas
|
||||||
$modelRegistry->registerSchemas();
|
$modelRegistry->registerSchemas();
|
||||||
|
|
||||||
$defaultOperationIdProcessor = new DefaultOperationId();
|
$analysis->process($generator->getProcessors());
|
||||||
$defaultOperationIdProcessor($analysis);
|
|
||||||
|
|
||||||
$analysis->process((new Generator())->getProcessors());
|
|
||||||
$analysis->validate();
|
$analysis->validate();
|
||||||
|
|
||||||
if (isset($item)) {
|
if (isset($item)) {
|
||||||
|
@ -30,6 +30,9 @@ final class ApiPlatformDescriber extends ExternalDocDescriber
|
|||||||
[DocumentationNormalizer::SPEC_VERSION => 3]
|
[DocumentationNormalizer::SPEC_VERSION => 3]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: remove this
|
||||||
|
// Temporary fix: zircote/swagger-php does no longer support 3.0.x with x > 0
|
||||||
|
unset($documentation['openapi']);
|
||||||
unset($documentation['basePath']);
|
unset($documentation['basePath']);
|
||||||
|
|
||||||
return $documentation;
|
return $documentation;
|
||||||
|
@ -17,6 +17,7 @@ use Nelmio\ApiDocBundle\Annotation\Security;
|
|||||||
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
|
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
|
||||||
use Nelmio\ApiDocBundle\Util\ControllerReflector;
|
use Nelmio\ApiDocBundle\Util\ControllerReflector;
|
||||||
use Nelmio\ApiDocBundle\Util\SetsContextTrait;
|
use Nelmio\ApiDocBundle\Util\SetsContextTrait;
|
||||||
|
use OpenApi\Analysers\AttributeAnnotationFactory;
|
||||||
use OpenApi\Annotations as OA;
|
use OpenApi\Annotations as OA;
|
||||||
use OpenApi\Generator;
|
use OpenApi\Generator;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
@ -67,12 +68,14 @@ final class OpenApiPhpDescriber
|
|||||||
$classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) {
|
$classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) {
|
||||||
return $v instanceof OA\AbstractAnnotation;
|
return $v instanceof OA\AbstractAnnotation;
|
||||||
});
|
});
|
||||||
|
$classAnnotations = array_merge($classAnnotations, $this->getAttributesAsAnnotation($declaringClass, $context));
|
||||||
$classAnnotations[$declaringClass->getName()] = $classAnnotations;
|
$classAnnotations[$declaringClass->getName()] = $classAnnotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
$annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) {
|
$annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) {
|
||||||
return $v instanceof OA\AbstractAnnotation;
|
return $v instanceof OA\AbstractAnnotation;
|
||||||
});
|
});
|
||||||
|
$annotations = array_merge($annotations, $this->getAttributesAsAnnotation($method, $context));
|
||||||
|
|
||||||
if (0 === count($annotations) && 0 === count($classAnnotations[$declaringClass->getName()])) {
|
if (0 === count($annotations) && 0 === count($classAnnotations[$declaringClass->getName()])) {
|
||||||
continue;
|
continue;
|
||||||
@ -107,6 +110,13 @@ final class OpenApiPhpDescriber
|
|||||||
|
|
||||||
if ($annotation instanceof Security) {
|
if ($annotation instanceof Security) {
|
||||||
$annotation->validate();
|
$annotation->validate();
|
||||||
|
|
||||||
|
if (null === $annotation->name) {
|
||||||
|
$mergeProperties->security = [];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$mergeProperties->security[] = [$annotation->name => $annotation->scopes];
|
$mergeProperties->security[] = [$annotation->name => $annotation->scopes];
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -190,4 +200,24 @@ final class OpenApiPhpDescriber
|
|||||||
|
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \ReflectionClass|\ReflectionMethod $reflection
|
||||||
|
*
|
||||||
|
* @return OA\AbstractAnnotation[]
|
||||||
|
*/
|
||||||
|
private function getAttributesAsAnnotation($reflection, \OpenApi\Context $context): array
|
||||||
|
{
|
||||||
|
// BC zircote/swagger-php < 4.0
|
||||||
|
if (!class_exists(AttributeAnnotationFactory::class)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$attributesFactory = new AttributeAnnotationFactory();
|
||||||
|
$attributes = $attributesFactory->build($reflection, $context);
|
||||||
|
// The attributes factory removes the context after executing so we need to set it back...
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ class OpenApiAnnotationsReader
|
|||||||
|
|
||||||
public function updateSchema(\ReflectionClass $reflectionClass, OA\Schema $schema): void
|
public function updateSchema(\ReflectionClass $reflectionClass, OA\Schema $schema): void
|
||||||
{
|
{
|
||||||
/** @var OA\Schema $oaSchema */
|
/** @var OA\Schema|null $oaSchema */
|
||||||
if (!$oaSchema = $this->annotationsReader->getClassAnnotation($reflectionClass, OA\Schema::class)) {
|
if (!$oaSchema = $this->getAnnotation($reflectionClass, OA\Schema::class)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +56,8 @@ class OpenApiAnnotationsReader
|
|||||||
|
|
||||||
public function getPropertyName($reflection, string $default): string
|
public function getPropertyName($reflection, string $default): string
|
||||||
{
|
{
|
||||||
/** @var OA\Property $oaProperty */
|
/** @var OA\Property|null $oaProperty */
|
||||||
if ($reflection instanceof \ReflectionProperty && !$oaProperty = $this->annotationsReader->getPropertyAnnotation($reflection, OA\Property::class)) {
|
if (!$oaProperty = $this->getAnnotation($reflection, OA\Property::class)) {
|
||||||
return $default;
|
|
||||||
} elseif ($reflection instanceof \ReflectionMethod && !$oaProperty = $this->annotationsReader->getMethodAnnotation($reflection, OA\Property::class)) {
|
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +76,8 @@ class OpenApiAnnotationsReader
|
|||||||
'filename' => $declaringClass->getFileName(),
|
'filename' => $declaringClass->getFileName(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
/** @var OA\Property $oaProperty */
|
/** @var OA\Property|null $oaProperty */
|
||||||
if ($reflection instanceof \ReflectionProperty && !$oaProperty = $this->annotationsReader->getPropertyAnnotation($reflection, OA\Property::class)) {
|
if (!$oaProperty = $this->getAnnotation($reflection, OA\Property::class)) {
|
||||||
return;
|
|
||||||
} elseif ($reflection instanceof \ReflectionMethod && !$oaProperty = $this->annotationsReader->getMethodAnnotation($reflection, OA\Property::class)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->setContext(null);
|
$this->setContext(null);
|
||||||
@ -95,4 +91,28 @@ class OpenApiAnnotationsReader
|
|||||||
|
|
||||||
$property->mergeProperties($oaProperty);
|
$property->mergeProperties($oaProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflection
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function getAnnotation($reflection, string $className)
|
||||||
|
{
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
if (null !== $attribute = $reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
|
||||||
|
return $attribute->newInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reflection instanceof \ReflectionClass) {
|
||||||
|
return $this->annotationsReader->getClassAnnotation($reflection, $className);
|
||||||
|
} elseif ($reflection instanceof \ReflectionProperty) {
|
||||||
|
return $this->annotationsReader->getPropertyAnnotation($reflection, $className);
|
||||||
|
} elseif ($reflection instanceof \ReflectionMethod) {
|
||||||
|
return $this->annotationsReader->getMethodAnnotation($reflection, $className);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
|
|||||||
);
|
);
|
||||||
$annotationsReader->updateDefinition($reflClass, $schema);
|
$annotationsReader->updateDefinition($reflClass, $schema);
|
||||||
|
|
||||||
$discriminatorMap = $this->doctrineReader->getClassAnnotation($reflClass, DiscriminatorMap::class);
|
$discriminatorMap = $this->getAnnotation($reflClass, DiscriminatorMap::class);
|
||||||
if ($discriminatorMap && Generator::UNDEFINED === $schema->discriminator) {
|
if ($discriminatorMap && Generator::UNDEFINED === $schema->discriminator) {
|
||||||
$this->applyOpenApiDiscriminator(
|
$this->applyOpenApiDiscriminator(
|
||||||
$model,
|
$model,
|
||||||
@ -192,6 +192,23 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
|
|||||||
throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $types[0]->getBuiltinType(), $model->getType()->getClassName(), $propertyName));
|
throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $types[0]->getBuiltinType(), $model->getType()->getClassName(), $propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function getAnnotation(\ReflectionClass $reflection, string $className)
|
||||||
|
{
|
||||||
|
if (false === class_exists($className)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (\PHP_VERSION_ID >= 80000) {
|
||||||
|
if (null !== $attribute = $reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
|
||||||
|
return $attribute->newInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->doctrineReader->getClassAnnotation($reflection, $className);
|
||||||
|
}
|
||||||
|
|
||||||
public function supports(Model $model): bool
|
public function supports(Model $model): bool
|
||||||
{
|
{
|
||||||
return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() && class_exists($model->getType()->getClassName());
|
return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() && class_exists($model->getType()->getClassName());
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
<?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\OpenApiPhp;
|
|
||||||
|
|
||||||
use OpenApi\Analysis;
|
|
||||||
use OpenApi\Annotations as OA;
|
|
||||||
use OpenApi\Generator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the OperationId processor from zircote/swagger-php as it breaks our documentation by setting non-unique operation ids.
|
|
||||||
* See https://github.com/zircote/swagger-php/pull/483#issuecomment-360739260 for the solution used here.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class DefaultOperationId
|
|
||||||
{
|
|
||||||
public function __invoke(Analysis $analysis)
|
|
||||||
{
|
|
||||||
$allOperations = $analysis->getAnnotationsOfType(OA\Operation::class);
|
|
||||||
|
|
||||||
foreach ($allOperations as $operation) {
|
|
||||||
if (Generator::UNDEFINED === $operation->operationId) {
|
|
||||||
$operation->operationId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -316,8 +316,6 @@ final class Util
|
|||||||
*/
|
*/
|
||||||
public static function createContext(array $properties = [], Context $parent = null): Context
|
public static function createContext(array $properties = [], Context $parent = null): Context
|
||||||
{
|
{
|
||||||
$properties['comment'] = ''; // TODO: remove this when https://github.com/zircote/swagger-php/commit/708a25208797ca05ebeae572bbccad8b13de14d8 is released
|
|
||||||
|
|
||||||
return new Context($properties, $parent);
|
return new Context($properties, $parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +341,7 @@ If you need more complex features, take a look at:
|
|||||||
customization
|
customization
|
||||||
commands
|
commands
|
||||||
faq
|
faq
|
||||||
|
security
|
||||||
|
|
||||||
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
|
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
|
||||||
.. _`willdurand/Hateoas`: https://github.com/willdurand/Hateoas
|
.. _`willdurand/Hateoas`: https://github.com/willdurand/Hateoas
|
||||||
|
43
Resources/doc/security.rst
Normal file
43
Resources/doc/security.rst
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
A default security policy can be added in ``nelmio_api_doc.documentation.security``
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
nelmio_api_doc:
|
||||||
|
documentation:
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
Bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
ApiKeyAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: X-API-Key
|
||||||
|
security:
|
||||||
|
Bearer: []
|
||||||
|
|
||||||
|
This will add the Bearer security policy to all registered paths.
|
||||||
|
|
||||||
|
Overriding Specific Paths
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
The security policy can be overriden for a path using the ``@Security`` annotation.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Security(name="ApiKeyAuth")
|
||||||
|
*/
|
||||||
|
|
||||||
|
Notice at the bottom of the docblock is a ``@Security`` annotation with a name of `ApiKeyAuth`. This will override the global security policy to only accept the ``ApiKeyAuth`` policy for this path.
|
||||||
|
|
||||||
|
You can also completely remove security from a path by providing ``@Security`` with a name of ``null``.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Security(name=null)
|
||||||
|
*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -44,6 +44,8 @@ final class FosRestDescriber implements RouteDescriberInterface
|
|||||||
$annotations = array_filter($annotations, static function ($value) {
|
$annotations = array_filter($annotations, static function ($value) {
|
||||||
return $value instanceof RequestParam || $value instanceof QueryParam;
|
return $value instanceof RequestParam || $value instanceof QueryParam;
|
||||||
});
|
});
|
||||||
|
$annotations = array_merge($annotations, $this->getAttributesAsAnnotation($reflectionMethod, RequestParam::class));
|
||||||
|
$annotations = array_merge($annotations, $this->getAttributesAsAnnotation($reflectionMethod, QueryParam::class));
|
||||||
|
|
||||||
foreach ($this->getOperations($api, $route) as $operation) {
|
foreach ($this->getOperations($api, $route) as $operation) {
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
@ -185,4 +187,21 @@ final class FosRestDescriber implements RouteDescriberInterface
|
|||||||
$schema->format = $format;
|
$schema->format = $format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return OA\AbstractAnnotation[]
|
||||||
|
*/
|
||||||
|
private function getAttributesAsAnnotation(\ReflectionMethod $reflection, string $className): array
|
||||||
|
{
|
||||||
|
$annotations = [];
|
||||||
|
if (\PHP_VERSION_ID < 80100) {
|
||||||
|
return $annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
|
||||||
|
$annotations[] = $attribute->newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $annotations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,13 +130,23 @@ final class FilteredRouteCollectionBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @var Areas|null $areas */
|
/** @var Areas|null $areas */
|
||||||
$areas = $this->annotationReader->getMethodAnnotation(
|
$areas = $this->getAttributesAsAnnotation($reflectionMethod, Areas::class)[0] ?? null;
|
||||||
$reflectionMethod,
|
|
||||||
Areas::class
|
|
||||||
);
|
|
||||||
|
|
||||||
if (null === $areas) {
|
if (null === $areas) {
|
||||||
$areas = $this->annotationReader->getClassAnnotation($reflectionMethod->getDeclaringClass(), Areas::class);
|
/** @var Areas|null $areas */
|
||||||
|
$areas = $this->getAttributesAsAnnotation($reflectionMethod->getDeclaringClass(), Areas::class)[0] ?? null;
|
||||||
|
|
||||||
|
if (null === $areas) {
|
||||||
|
/** @var Areas|null $areas */
|
||||||
|
$areas = $this->annotationReader->getMethodAnnotation(
|
||||||
|
$reflectionMethod,
|
||||||
|
Areas::class
|
||||||
|
);
|
||||||
|
|
||||||
|
if (null === $areas) {
|
||||||
|
$areas = $this->annotationReader->getClassAnnotation($reflectionMethod->getDeclaringClass(), Areas::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (null !== $areas) ? $areas->has($this->area) : false;
|
return (null !== $areas) ? $areas->has($this->area) : false;
|
||||||
@ -168,4 +178,23 @@ final class FilteredRouteCollectionBuilder
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \ReflectionClass|\ReflectionMethod $reflection
|
||||||
|
*
|
||||||
|
* @return Areas[]
|
||||||
|
*/
|
||||||
|
private function getAttributesAsAnnotation($reflection, string $className): array
|
||||||
|
{
|
||||||
|
$annotations = [];
|
||||||
|
if (\PHP_VERSION_ID < 80100) {
|
||||||
|
return $annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
|
||||||
|
$annotations[] = $attribute->newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $annotations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,257 +11,20 @@
|
|||||||
|
|
||||||
namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
|
namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
|
||||||
|
|
||||||
use Nelmio\ApiDocBundle\Annotation\Areas;
|
|
||||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
|
||||||
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\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\UserType;
|
|
||||||
use OpenApi\Annotations as OA;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
/**
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
* @Route("/api", name="api_", host="api.example.com")
|
|
||||||
*/
|
|
||||||
class ApiController
|
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* @OA\Get(
|
* @Route("/api", name="api_", host="api.example.com")
|
||||||
* @OA\Response(
|
|
||||||
* response="200",
|
|
||||||
* description="Success",
|
|
||||||
* @Model(type=Article::class, groups={"light"}))
|
|
||||||
* )
|
|
||||||
* )
|
|
||||||
* @OA\Parameter(ref="#/components/parameters/test")
|
|
||||||
* @Route("/article/{id}", methods={"GET"})
|
|
||||||
* @OA\Parameter(name="Accept-Version", in="header", @OA\Schema(type="string"))
|
|
||||||
* @OA\Parameter(name="Application-Name", in="header", @OA\Schema(type="string"))
|
|
||||||
*/
|
*/
|
||||||
public function fetchArticleAction()
|
class ApiController extends ApiController81
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
/**
|
/**
|
||||||
* The method LINK is not supported by OpenAPI so the method will be ignored.
|
* @Route("/api", name="api_", host="api.example.com")
|
||||||
*
|
|
||||||
* @Route("/swagger", methods={"GET", "LINK"})
|
|
||||||
* @Route("/swagger2", methods={"GET"})
|
|
||||||
* @Operation(
|
|
||||||
* @OA\Response(response="201", description="An example resource")
|
|
||||||
* )
|
|
||||||
* @OA\Get(
|
|
||||||
* path="/api/swagger2",
|
|
||||||
* @OA\Parameter(name="Accept-Version", in="header", @OA\Schema(type="string"))
|
|
||||||
* )
|
|
||||||
* @OA\Post(
|
|
||||||
* path="/api/swagger2",
|
|
||||||
* @OA\Response(response="203", description="but 203 is not actually allowed (wrong method)")
|
|
||||||
* )
|
|
||||||
*/
|
*/
|
||||||
public function swaggerAction()
|
class ApiController extends ApiController80
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/swagger/implicit", methods={"GET", "POST"})
|
|
||||||
* @OA\Response(
|
|
||||||
* response="201",
|
|
||||||
* description="Operation automatically detected",
|
|
||||||
* @Model(type=User::class)
|
|
||||||
* ),
|
|
||||||
* @OA\RequestBody(
|
|
||||||
* description="This is a request body",
|
|
||||||
* @OA\JsonContent(
|
|
||||||
* type="array",
|
|
||||||
* @OA\Items(ref=@Model(type=User::class))
|
|
||||||
* )
|
|
||||||
* )
|
|
||||||
* @OA\Tag(name="implicit")
|
|
||||||
*/
|
|
||||||
public function implicitSwaggerAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/test/users/{user}", methods={"POST"}, schemes={"https"}, requirements={"user"="/foo/"})
|
|
||||||
* @OA\Response(
|
|
||||||
* response="201",
|
|
||||||
* description="Operation automatically detected",
|
|
||||||
* @Model(type=User::class)
|
|
||||||
* ),
|
|
||||||
* @OA\RequestBody(
|
|
||||||
* description="This is a request body",
|
|
||||||
* @Model(type=UserType::class, options={"bar": "baz"}))
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
public function submitUserTypeAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/test/{user}", methods={"GET"}, schemes={"https"}, requirements={"user"="/foo/"})
|
|
||||||
* @OA\Response(response=200, description="sucessful")
|
|
||||||
*/
|
|
||||||
public function userAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This action is deprecated.
|
|
||||||
*
|
|
||||||
* Please do not use this action.
|
|
||||||
*
|
|
||||||
* @Route("/deprecated", methods={"GET"})
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function deprecatedAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This action is not documented. It is excluded by the config.
|
|
||||||
*
|
|
||||||
* @Route("/admin", methods={"GET"})
|
|
||||||
*/
|
|
||||||
public function adminAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @OA\Get(
|
|
||||||
* path="/filtered",
|
|
||||||
* @OA\Response(response="201", description="")
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
public function filteredAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/form", methods={"POST"})
|
|
||||||
* @OA\RequestBody(
|
|
||||||
* description="Request content",
|
|
||||||
* @Model(type=DummyType::class))
|
|
||||||
* )
|
|
||||||
* @OA\Response(response="201", description="")
|
|
||||||
*/
|
|
||||||
public function formAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/security")
|
|
||||||
* @OA\Response(response="201", description="")
|
|
||||||
* @Security(name="api_key")
|
|
||||||
* @Security(name="basic")
|
|
||||||
* @Security(name="oauth2", scopes={"scope_1"})
|
|
||||||
*/
|
|
||||||
public function securityAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/swagger/symfonyConstraints", methods={"GET"})
|
|
||||||
* @OA\Response(
|
|
||||||
* response="201",
|
|
||||||
* description="Used for symfony constraints test",
|
|
||||||
* @Model(type=SymfonyConstraints::class)
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
public function symfonyConstraintsAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/swagger/symfonyConstraintsWithValidationGroups", methods={"GET"})
|
|
||||||
* @OA\Response(
|
|
||||||
* response="201",
|
|
||||||
* description="Used for symfony constraints with validation groups test",
|
|
||||||
* @Model(type=SymfonyConstraintsWithValidationGroups::class, groups={"test"})
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
public function symfonyConstraintsWithGroupsAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @OA\Response(
|
|
||||||
* response="200",
|
|
||||||
* description="Success",
|
|
||||||
* ref="#/components/schemas/Test"
|
|
||||||
* ),
|
|
||||||
* @OA\Response(
|
|
||||||
* response="201",
|
|
||||||
* ref="#/components/responses/201"
|
|
||||||
* )
|
|
||||||
* @Route("/configReference", methods={"GET"})
|
|
||||||
*/
|
|
||||||
public function configReferenceAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/multi-annotations", methods={"GET", "POST"})
|
|
||||||
* @OA\Get(description="This is the get operation")
|
|
||||||
* @OA\Post(description="This is post")
|
|
||||||
*
|
|
||||||
* @OA\Response(response=200, description="Worked well!", @Model(type=DummyType::class))
|
|
||||||
*/
|
|
||||||
public function operationsWithOtherAnnotations()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/areas/new", methods={"GET", "POST"})
|
|
||||||
*
|
|
||||||
* @Areas({"area", "area2"})
|
|
||||||
*/
|
|
||||||
public function newAreaAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/compound", methods={"GET", "POST"})
|
|
||||||
*
|
|
||||||
* @OA\Response(response=200, description="Worked well!", @Model(type=CompoundEntity::class))
|
|
||||||
*/
|
|
||||||
public function compoundEntityAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/discriminator-mapping", methods={"GET", "POST"})
|
|
||||||
*
|
|
||||||
* @OA\Response(response=200, description="Worked well!", @Model(type=SymfonyDiscriminator::class))
|
|
||||||
*/
|
|
||||||
public function discriminatorMappingAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/named_route-operation-id", name="named_route_operation_id", methods={"GET", "POST"})
|
|
||||||
*
|
|
||||||
* @OA\Response(response=200, description="success")
|
|
||||||
*/
|
|
||||||
public function namedRouteOperationIdAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/custom-operation-id", methods={"GET", "POST"})
|
|
||||||
*
|
|
||||||
* @Operation(operationId="custom-operation-id")
|
|
||||||
* @OA\Response(response=200, description="success")
|
|
||||||
*/
|
|
||||||
public function customOperationIdAction()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
275
Tests/Functional/Controller/ApiController80.php
Normal file
275
Tests/Functional/Controller/ApiController80.php
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Areas;
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||||
|
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\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\UserType;
|
||||||
|
use OpenApi\Annotations as OA;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class ApiController80
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* @OA\Response(
|
||||||
|
* response="200",
|
||||||
|
* description="Success",
|
||||||
|
* @Model(type=Article::class, groups={"light"}))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* @OA\Parameter(ref="#/components/parameters/test")
|
||||||
|
* @Route("/article/{id}", methods={"GET"})
|
||||||
|
* @OA\Parameter(name="Accept-Version", in="header", @OA\Schema(type="string"))
|
||||||
|
* @OA\Parameter(name="Application-Name", in="header", @OA\Schema(type="string"))
|
||||||
|
*/
|
||||||
|
public function fetchArticleAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method LINK is not supported by OpenAPI so the method will be ignored.
|
||||||
|
*
|
||||||
|
* @Route("/swagger", methods={"GET", "LINK"})
|
||||||
|
* @Route("/swagger2", methods={"GET"})
|
||||||
|
* @Operation(
|
||||||
|
* @OA\Response(response="201", description="An example resource")
|
||||||
|
* )
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/swagger2",
|
||||||
|
* @OA\Parameter(name="Accept-Version", in="header", @OA\Schema(type="string"))
|
||||||
|
* )
|
||||||
|
* @OA\Post(
|
||||||
|
* path="/api/swagger2",
|
||||||
|
* @OA\Response(response="203", description="but 203 is not actually allowed (wrong method)")
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function swaggerAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/swagger/implicit", methods={"GET", "POST"})
|
||||||
|
* @OA\Response(
|
||||||
|
* response="201",
|
||||||
|
* description="Operation automatically detected",
|
||||||
|
* @Model(type=User::class)
|
||||||
|
* ),
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* description="This is a request body",
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* type="array",
|
||||||
|
* @OA\Items(ref=@Model(type=User::class))
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* @OA\Tag(name="implicit")
|
||||||
|
*/
|
||||||
|
public function implicitSwaggerAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/test/users/{user}", methods={"POST"}, schemes={"https"}, requirements={"user"="/foo/"})
|
||||||
|
* @OA\Response(
|
||||||
|
* response="201",
|
||||||
|
* description="Operation automatically detected",
|
||||||
|
* @Model(type=User::class)
|
||||||
|
* ),
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* description="This is a request body",
|
||||||
|
* @Model(type=UserType::class, options={"bar": "baz"}))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function submitUserTypeAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/test/{user}", methods={"GET"}, schemes={"https"}, requirements={"user"="/foo/"})
|
||||||
|
* @OA\Response(response=200, description="sucessful")
|
||||||
|
*/
|
||||||
|
public function userAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action is deprecated.
|
||||||
|
*
|
||||||
|
* Please do not use this action.
|
||||||
|
*
|
||||||
|
* @Route("/deprecated", methods={"GET"})
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public function deprecatedAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action is not documented. It is excluded by the config.
|
||||||
|
*
|
||||||
|
* @Route("/admin", methods={"GET"})
|
||||||
|
*/
|
||||||
|
public function adminAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/filtered",
|
||||||
|
* @OA\Response(response="201", description="")
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function filteredAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/form", methods={"POST"})
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* description="Request content",
|
||||||
|
* @Model(type=DummyType::class))
|
||||||
|
* )
|
||||||
|
* @OA\Response(response="201", description="")
|
||||||
|
*/
|
||||||
|
public function formAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/security")
|
||||||
|
* @OA\Response(response="201", description="")
|
||||||
|
* @Security(name="api_key")
|
||||||
|
* @Security(name="basic")
|
||||||
|
* @Security(name="oauth2", scopes={"scope_1"})
|
||||||
|
*/
|
||||||
|
public function securityAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/securityOverride")
|
||||||
|
* @OA\Response(response="201", description="")
|
||||||
|
* @Security(name="api_key")
|
||||||
|
* @Security(name=null)
|
||||||
|
*/
|
||||||
|
public function securityActionOverride()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/swagger/symfonyConstraints", methods={"GET"})
|
||||||
|
* @OA\Response(
|
||||||
|
* response="201",
|
||||||
|
* description="Used for symfony constraints test",
|
||||||
|
* @Model(type=SymfonyConstraints::class)
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function symfonyConstraintsAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Response(
|
||||||
|
* response="200",
|
||||||
|
* description="Success",
|
||||||
|
* ref="#/components/schemas/Test"
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response="201",
|
||||||
|
* ref="#/components/responses/201"
|
||||||
|
* )
|
||||||
|
* @Route("/configReference", methods={"GET"})
|
||||||
|
*/
|
||||||
|
public function configReferenceAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/multi-annotations", methods={"GET", "POST"})
|
||||||
|
* @OA\Get(description="This is the get operation")
|
||||||
|
* @OA\Post(description="This is post")
|
||||||
|
*
|
||||||
|
* @OA\Response(response=200, description="Worked well!", @Model(type=DummyType::class))
|
||||||
|
*/
|
||||||
|
public function operationsWithOtherAnnotations()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/areas/new", methods={"GET", "POST"})
|
||||||
|
*
|
||||||
|
* @Areas({"area", "area2"})
|
||||||
|
*/
|
||||||
|
public function newAreaAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/compound", methods={"GET", "POST"})
|
||||||
|
*
|
||||||
|
* @OA\Response(response=200, description="Worked well!", @Model(type=CompoundEntity::class))
|
||||||
|
*/
|
||||||
|
public function compoundEntityAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/discriminator-mapping", methods={"GET", "POST"})
|
||||||
|
*
|
||||||
|
* @OA\Response(response=200, description="Worked well!", @Model(type=SymfonyDiscriminator::class))
|
||||||
|
*/
|
||||||
|
public function discriminatorMappingAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/named_route-operation-id", name="named_route_operation_id", methods={"GET", "POST"})
|
||||||
|
*
|
||||||
|
* @OA\Response(response=200, description="success")
|
||||||
|
*/
|
||||||
|
public function namedRouteOperationIdAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/custom-operation-id", methods={"GET", "POST"})
|
||||||
|
*
|
||||||
|
* @OA\Get(operationId="get-custom-operation-id")
|
||||||
|
* @OA\Post(operationId="post-custom-operation-id")
|
||||||
|
* @OA\Response(response=200, description="success")
|
||||||
|
*/
|
||||||
|
public function customOperationIdAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/swagger/symfonyConstraintsWithValidationGroups", methods={"GET"})
|
||||||
|
* @OA\Response(
|
||||||
|
* response="201",
|
||||||
|
* description="Used for symfony constraints with validation groups test",
|
||||||
|
* @Model(type=SymfonyConstraintsWithValidationGroups::class, groups={"test"})
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function symfonyConstraintsWithGroupsAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
68
Tests/Functional/Controller/ApiController81.php
Normal file
68
Tests/Functional/Controller/ApiController81.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Areas;
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Security;
|
||||||
|
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class ApiController81 extends ApiController80
|
||||||
|
{
|
||||||
|
#[OA\Get(responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: '200',
|
||||||
|
description: 'Success',
|
||||||
|
attachables: [
|
||||||
|
new Model(type: Article::class, groups: ['light']),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])]
|
||||||
|
#[OA\Parameter(ref: '#/components/parameters/test')]
|
||||||
|
#[Route('/article_attributes/{id}', methods: ['GET'])]
|
||||||
|
#[OA\Parameter(name: 'Accept-Version', in: 'header', attachables: [new OA\Schema(type: 'string')])]
|
||||||
|
public function fetchArticleActionWithAttributes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Areas(['area', 'area2'])]
|
||||||
|
#[Route('/areas_attributes/new', methods: ['GET', 'POST'])]
|
||||||
|
public function newAreaActionAttributes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/security_attributes')]
|
||||||
|
#[OA\Response(response: '201', description: '')]
|
||||||
|
#[Security(name: 'api_key')]
|
||||||
|
#[Security(name: 'basic')]
|
||||||
|
#[Security(name: 'oauth2', scopes: ['scope_1'])]
|
||||||
|
public function securityActionAttributes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/security_override_attributes')]
|
||||||
|
#[OA\Response(response: '201', description: '')]
|
||||||
|
#[Security(name: 'api_key')]
|
||||||
|
#[Security(name: null)]
|
||||||
|
public function securityOverrideActionAttributes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/inline_path_parameters')]
|
||||||
|
#[OA\Response(response: '200', description: '')]
|
||||||
|
public function inlinePathParameters(
|
||||||
|
#[OA\PathParameter] string $product_id
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@ -11,30 +11,20 @@
|
|||||||
|
|
||||||
namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
|
namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
|
||||||
|
|
||||||
use FOS\RestBundle\Controller\Annotations\QueryParam;
|
|
||||||
use FOS\RestBundle\Controller\Annotations\RequestParam;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Validator\Constraints\DateTime;
|
|
||||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
|
||||||
use Symfony\Component\Validator\Constraints\Regex;
|
|
||||||
|
|
||||||
/**
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
* @Route("/api", host="api.example.com")
|
|
||||||
*/
|
|
||||||
class FOSRestController
|
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* @Route("/fosrest.{_format}", methods={"POST"})
|
* @Route("/api", host="api.example.com")
|
||||||
* @QueryParam(name="foo", requirements=@Regex("/^\d+$/"))
|
|
||||||
* @QueryParam(name="mapped", map=true)
|
|
||||||
* @RequestParam(name="Barraa", key="bar", requirements="\d+")
|
|
||||||
* @RequestParam(name="baz", requirements=@IsTrue)
|
|
||||||
* @RequestParam(name="datetime", requirements=@DateTime("Y-m-d\TH:i:sP"))
|
|
||||||
* @RequestParam(name="datetimeAlt", requirements=@DateTime("c"))
|
|
||||||
* @RequestParam(name="datetimeNoFormat", requirements=@DateTime())
|
|
||||||
* @RequestParam(name="date", requirements=@DateTime("Y-m-d"))
|
|
||||||
*/
|
*/
|
||||||
public function fosrestAction()
|
class FOSRestController extends FOSRestController81
|
||||||
|
{
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* @Route("/api", host="api.example.com")
|
||||||
|
*/
|
||||||
|
class FOSRestController extends FOSRestController80
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
Tests/Functional/Controller/FOSRestController80.php
Normal file
37
Tests/Functional/Controller/FOSRestController80.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use FOS\RestBundle\Controller\Annotations\QueryParam;
|
||||||
|
use FOS\RestBundle\Controller\Annotations\RequestParam;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Validator\Constraints\DateTime;
|
||||||
|
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||||
|
use Symfony\Component\Validator\Constraints\Regex;
|
||||||
|
|
||||||
|
class FOSRestController80
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/fosrest.{_format}", methods={"POST"})
|
||||||
|
* @QueryParam(name="foo", requirements=@Regex("/^\d+$/"))
|
||||||
|
* @QueryParam(name="mapped", map=true)
|
||||||
|
* @RequestParam(name="Barraa", key="bar", requirements="\d+")
|
||||||
|
* @RequestParam(name="baz", requirements=@IsTrue)
|
||||||
|
* @RequestParam(name="datetime", requirements=@DateTime("Y-m-d\TH:i:sP"))
|
||||||
|
* @RequestParam(name="datetimeAlt", requirements=@DateTime("c"))
|
||||||
|
* @RequestParam(name="datetimeNoFormat", requirements=@DateTime())
|
||||||
|
* @RequestParam(name="date", requirements=@DateTime("Y-m-d"))
|
||||||
|
*/
|
||||||
|
public function fosrestAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
35
Tests/Functional/Controller/FOSRestController81.php
Normal file
35
Tests/Functional/Controller/FOSRestController81.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\Controller;
|
||||||
|
|
||||||
|
use FOS\RestBundle\Controller\Annotations\QueryParam;
|
||||||
|
use FOS\RestBundle\Controller\Annotations\RequestParam;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Validator\Constraints\DateTime;
|
||||||
|
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||||
|
use Symfony\Component\Validator\Constraints\Regex;
|
||||||
|
|
||||||
|
class FOSRestController81 extends FOSRestController80
|
||||||
|
{
|
||||||
|
#[Route('/fosrest_attributes.{_format}', methods: ['POST'])]
|
||||||
|
#[QueryParam(name: 'foo', requirements: new Regex('/^\d+$/'))]
|
||||||
|
#[QueryParam(name: 'mapped', map: true)]
|
||||||
|
#[RequestParam(name: 'Barraa', key: 'bar', requirements: '\d+')]
|
||||||
|
#[RequestParam(name: 'baz', requirements: new IsTrue())]
|
||||||
|
#[RequestParam(name: 'datetime', requirements: new DateTime('Y-m-d\TH:i:sP'))]
|
||||||
|
#[RequestParam(name: 'datetimeAlt', requirements: new DateTime('c'))]
|
||||||
|
#[RequestParam(name: 'datetimeNoFormat', requirements: new DateTime())]
|
||||||
|
#[RequestParam(name: 'date', requirements: new DateTime('Y-m-d'))]
|
||||||
|
public function fosrestAttributesAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -23,9 +23,12 @@ class FOSRestTest extends WebTestCase
|
|||||||
static::createClient([], ['HTTP_HOST' => 'api.example.com']);
|
static::createClient([], ['HTTP_HOST' => 'api.example.com']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFOSRestAction()
|
/**
|
||||||
|
* @dataProvider provideRoute
|
||||||
|
*/
|
||||||
|
public function testFOSRestAction(string $route)
|
||||||
{
|
{
|
||||||
$operation = $this->getOperation('/api/fosrest', 'post');
|
$operation = $this->getOperation($route, 'post');
|
||||||
|
|
||||||
$this->assertHasParameter('foo', 'query', $operation);
|
$this->assertHasParameter('foo', 'query', $operation);
|
||||||
$this->assertInstanceOf(OA\RequestBody::class, $operation->requestBody);
|
$this->assertInstanceOf(OA\RequestBody::class, $operation->requestBody);
|
||||||
@ -66,4 +69,13 @@ class FOSRestTest extends WebTestCase
|
|||||||
// The _format path attribute should be removed
|
// The _format path attribute should be removed
|
||||||
$this->assertNotHasParameter('_format', 'path', $operation);
|
$this->assertNotHasParameter('_format', 'path', $operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function provideRoute(): iterable
|
||||||
|
{
|
||||||
|
yield 'Annotations' => ['/api/fosrest'];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
yield 'Attributes' => ['/api/fosrest_attributes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,12 @@ class FunctionalTest extends WebTestCase
|
|||||||
$this->assertNotHasPath('/api/admin', $api);
|
$this->assertNotHasPath('/api/admin', $api);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFetchArticleAction()
|
/**
|
||||||
|
* @dataProvider provideArticleRoute
|
||||||
|
*/
|
||||||
|
public function testFetchArticleAction(string $articleRoute)
|
||||||
{
|
{
|
||||||
$operation = $this->getOperation('/api/article/{id}', 'get');
|
$operation = $this->getOperation($articleRoute, 'get');
|
||||||
|
|
||||||
$this->assertHasResponse('200', $operation);
|
$this->assertHasResponse('200', $operation);
|
||||||
$response = $this->getOperationResponse($operation, '200');
|
$response = $this->getOperationResponse($operation, '200');
|
||||||
@ -56,6 +59,15 @@ class FunctionalTest extends WebTestCase
|
|||||||
$this->assertNotHasProperty('author', Util::getProperty($articleModel, 'author'));
|
$this->assertNotHasProperty('author', Util::getProperty($articleModel, 'author'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function provideArticleRoute(): iterable
|
||||||
|
{
|
||||||
|
yield 'Annotations' => ['/api/article/{id}'];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
yield 'Attributes' => ['/api/article_attributes/{id}'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testFilteredAction()
|
public function testFilteredAction()
|
||||||
{
|
{
|
||||||
$openApi = $this->getOpenApiDefinition();
|
$openApi = $this->getOpenApiDefinition();
|
||||||
@ -333,9 +345,12 @@ class FunctionalTest extends WebTestCase
|
|||||||
], json_decode($this->getModel('DummyType')->toJson(), true));
|
], json_decode($this->getModel('DummyType')->toJson(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSecurityAction()
|
/**
|
||||||
|
* @dataProvider provideSecurityRoute
|
||||||
|
*/
|
||||||
|
public function testSecurityAction(string $route)
|
||||||
{
|
{
|
||||||
$operation = $this->getOperation('/api/security', 'get');
|
$operation = $this->getOperation($route, 'get');
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
['api_key' => []],
|
['api_key' => []],
|
||||||
@ -345,6 +360,46 @@ class FunctionalTest extends WebTestCase
|
|||||||
$this->assertEquals($expected, $operation->security);
|
$this->assertEquals($expected, $operation->security);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function provideSecurityRoute(): iterable
|
||||||
|
{
|
||||||
|
yield 'Annotations' => ['/api/security'];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
yield 'Attributes' => ['/api/security_attributes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideSecurityOverrideRoute
|
||||||
|
*/
|
||||||
|
public function testSecurityOverrideAction(string $route)
|
||||||
|
{
|
||||||
|
$operation = $this->getOperation($route, 'get');
|
||||||
|
$this->assertEquals([], $operation->security);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideSecurityOverrideRoute(): iterable
|
||||||
|
{
|
||||||
|
yield 'Annotations' => ['/api/securityOverride'];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
yield 'Attributes' => ['/api/security_override_attributes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInlinePHP81Parameters()
|
||||||
|
{
|
||||||
|
if (\PHP_VERSION_ID < 80100) {
|
||||||
|
$this->markTestSkipped('Attributes require PHP 8.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$operation = $this->getOperation('/api/inline_path_parameters', 'get');
|
||||||
|
$this->assertCount(1, $operation->parameters);
|
||||||
|
$this->assertInstanceOf(OA\PathParameter::class, $operation->parameters[0]);
|
||||||
|
$this->assertSame($operation->parameters[0]->name, 'product_id');
|
||||||
|
$this->assertSame($operation->parameters[0]->schema->type, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
public function testClassSecurityAction()
|
public function testClassSecurityAction()
|
||||||
{
|
{
|
||||||
$operation = $this->getOperation('/api/security/class', 'get');
|
$operation = $this->getOperation('/api/security/class', 'get');
|
||||||
@ -522,10 +577,10 @@ class FunctionalTest extends WebTestCase
|
|||||||
public function testCustomOperationId()
|
public function testCustomOperationId()
|
||||||
{
|
{
|
||||||
$operation = $this->getOperation('/api/custom-operation-id', 'get');
|
$operation = $this->getOperation('/api/custom-operation-id', 'get');
|
||||||
$this->assertEquals('custom-operation-id', $operation->operationId);
|
$this->assertEquals('get-custom-operation-id', $operation->operationId);
|
||||||
|
|
||||||
$operation = $this->getOperation('/api/custom-operation-id', 'post');
|
$operation = $this->getOperation('/api/custom-operation-id', 'post');
|
||||||
$this->assertEquals('custom-operation-id', $operation->operationId);
|
$this->assertEquals('post-custom-operation-id', $operation->operationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
45
Tests/Functional/Resources/routes.yaml
Normal file
45
Tests/Functional/Resources/routes.yaml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Resources
|
||||||
|
test:
|
||||||
|
resource: ../Controller/TestController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
api:
|
||||||
|
resource: ../Controller/ApiController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
class_api:
|
||||||
|
resource: ../Controller/ClassApiController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
undocumented:
|
||||||
|
resource: ../Controller/UndocumentedController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
invokable:
|
||||||
|
resource: ../Controller/InvokableController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
fos_rest:
|
||||||
|
resource: ../Controller/FOSRestController.php
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
|
||||||
|
api_platform:
|
||||||
|
resource: .
|
||||||
|
prefix: /api
|
||||||
|
type: api_platform
|
||||||
|
|
||||||
|
# Controllers
|
||||||
|
doc_area:
|
||||||
|
path: /docs/{area}
|
||||||
|
controller: nelmio_api_doc.controller.swagger_ui
|
||||||
|
defaults:
|
||||||
|
area: default
|
||||||
|
|
||||||
|
doc_json:
|
||||||
|
path: /docs.json
|
||||||
|
controller: nelmio_api_doc.controller.swagger_json
|
||||||
|
|
||||||
|
doc_yaml:
|
||||||
|
path: /docs.yaml
|
||||||
|
controller: nelmio_api_doc.controller.swagger_yaml
|
@ -31,7 +31,7 @@ use Symfony\Component\Config\Loader\LoaderInterface;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\HttpKernel\Kernel;
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
use Symfony\Component\Routing\RouteCollectionBuilder;
|
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||||
|
|
||||||
class TestKernel extends Kernel
|
class TestKernel extends Kernel
|
||||||
@ -81,39 +81,42 @@ class TestKernel extends Kernel
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function configureRoutes(RouteCollectionBuilder $routes)
|
protected function configureRoutes($routes)
|
||||||
{
|
{
|
||||||
$routes->import(__DIR__.'/Controller/TestController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Resources/routes.yaml', '/', 'yaml');
|
||||||
$routes->import(__DIR__.'/Controller/ApiController.php', '/', 'annotation');
|
|
||||||
$routes->import(__DIR__.'/Controller/ClassApiController.php', '/', 'annotation');
|
|
||||||
$routes->import(__DIR__.'/Controller/UndocumentedController.php', '/', 'annotation');
|
|
||||||
$routes->import(__DIR__.'/Controller/InvokableController.php', '/', 'annotation');
|
|
||||||
$routes->import('', '/api', 'api_platform');
|
|
||||||
$routes->add('/docs/{area}', 'nelmio_api_doc.controller.swagger_ui')->setDefault('area', 'default');
|
|
||||||
$routes->add('/docs.json', 'nelmio_api_doc.controller.swagger_json');
|
|
||||||
$routes->add('/docs.yaml', 'nelmio_api_doc.controller.swagger_yaml');
|
|
||||||
$routes->import(__DIR__.'/Controller/FOSRestController.php', '/', 'annotation');
|
|
||||||
|
|
||||||
if (class_exists(SerializedName::class)) {
|
if (class_exists(SerializedName::class)) {
|
||||||
$routes->import(__DIR__.'/Controller/SerializedNameController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Controller/SerializedNameController.php', '/', 'annotation');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->flags & self::USE_JMS) {
|
if ($this->flags & self::USE_JMS) {
|
||||||
$routes->import(__DIR__.'/Controller/JMSController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Controller/JMSController.php', '/', 'annotation');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->flags & self::USE_BAZINGA) {
|
if ($this->flags & self::USE_BAZINGA) {
|
||||||
$routes->import(__DIR__.'/Controller/BazingaController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Controller/BazingaController.php', '/', 'annotation');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new \ReflectionMethod(Embedded::class, 'getType');
|
new \ReflectionMethod(Embedded::class, 'getType');
|
||||||
$routes->import(__DIR__.'/Controller/BazingaTypedController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Controller/BazingaTypedController.php', '/', 'annotation');
|
||||||
} catch (\ReflectionException $e) {
|
} catch (\ReflectionException $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->flags & self::ERROR_ARRAY_ITEMS) {
|
if ($this->flags & self::ERROR_ARRAY_ITEMS) {
|
||||||
$routes->import(__DIR__.'/Controller/ArrayItemsErrorController.php', '/', 'annotation');
|
$this->import($routes, __DIR__.'/Controller/ArrayItemsErrorController.php', '/', 'annotation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BC for sf < 5.1.
|
||||||
|
*/
|
||||||
|
private function import($routes, $resource, $prefix, $type)
|
||||||
|
{
|
||||||
|
if ($routes instanceof RoutingConfigurator) {
|
||||||
|
$routes->withPath($prefix)->import($resource, $type);
|
||||||
|
} else {
|
||||||
|
$routes->import($resource, $prefix, $type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +189,20 @@ class TestKernel extends Kernel
|
|||||||
'info' => [
|
'info' => [
|
||||||
'title' => 'My Default App',
|
'title' => 'My Default App',
|
||||||
],
|
],
|
||||||
|
'paths' => [
|
||||||
|
// Ensures we can define routes in Yaml without defining OperationIds
|
||||||
|
// See https://github.com/zircote/swagger-php/issues/1153
|
||||||
|
'/api/test-from-yaml' => ['get' => [
|
||||||
|
'responses' => [
|
||||||
|
200 => ['description' => 'success'],
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
'/api/test-from-yaml2' => ['get' => [
|
||||||
|
'responses' => [
|
||||||
|
200 => ['description' => 'success'],
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
],
|
||||||
'components' => [
|
'components' => [
|
||||||
'schemas' => [
|
'schemas' => [
|
||||||
'Test' => [
|
'Test' => [
|
||||||
|
@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional;
|
|||||||
use OpenApi\Annotations as OA;
|
use OpenApi\Annotations as OA;
|
||||||
use OpenApi\Generator;
|
use OpenApi\Generator;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
|
||||||
class WebTestCase extends BaseWebTestCase
|
class WebTestCase extends BaseWebTestCase
|
||||||
@ -168,4 +169,16 @@ class WebTestCase extends BaseWebTestCase
|
|||||||
sprintf('Failed asserting that property "%s" does not exist.', $property)
|
sprintf('Failed asserting that property "%s" does not exist.', $property)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BC symfony < 5.3.
|
||||||
|
*/
|
||||||
|
protected static function getContainer(): ContainerInterface
|
||||||
|
{
|
||||||
|
if (method_exists(parent::class, 'getContainer')) {
|
||||||
|
return parent::getContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$container;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
68
Tests/ModelDescriber/Annotations/AnnotationReaderTest.php
Normal file
68
Tests/ModelDescriber/Annotations/AnnotationReaderTest.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?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\ModelDescriber\Annotations;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
|
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||||
|
use Nelmio\ApiDocBundle\ModelDescriber\Annotations\OpenApiAnnotationsReader;
|
||||||
|
use OpenApi\Annotations as OA;
|
||||||
|
use OpenApi\Attributes as OAattr;
|
||||||
|
use OpenApi\Generator;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class AnnotationReaderTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param object $entity
|
||||||
|
* @dataProvider provideProperty
|
||||||
|
*/
|
||||||
|
public function testProperty($entity)
|
||||||
|
{
|
||||||
|
$schema = new OA\Schema([]);
|
||||||
|
$schema->merge([new OA\Property(['property' => 'property1'])]);
|
||||||
|
$schema->merge([new OA\Property(['property' => 'property2'])]);
|
||||||
|
|
||||||
|
$registry = new ModelRegistry([], new OA\OpenApi([]), []);
|
||||||
|
$symfonyConstraintAnnotationReader = new OpenApiAnnotationsReader(new AnnotationReader(), $registry, ['json']);
|
||||||
|
$symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]);
|
||||||
|
$symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property2'), $schema->properties[1]);
|
||||||
|
|
||||||
|
$this->assertEquals($schema->properties[0]->example, 1);
|
||||||
|
$this->assertEquals($schema->properties[0]->description, Generator::UNDEFINED);
|
||||||
|
|
||||||
|
$this->assertEquals($schema->properties[1]->example, 'some example');
|
||||||
|
$this->assertEquals($schema->properties[1]->description, 'some description');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideProperty(): iterable
|
||||||
|
{
|
||||||
|
yield 'Annotations' => [new class() {
|
||||||
|
/**
|
||||||
|
* @OA\Property(example=1)
|
||||||
|
*/
|
||||||
|
private $property1;
|
||||||
|
/**
|
||||||
|
* @OA\Property(example="some example", description="some description")
|
||||||
|
*/
|
||||||
|
private $property2;
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
yield 'Attributes' => [new class() {
|
||||||
|
#[OAattr\Property(example: 1)]
|
||||||
|
private $property1;
|
||||||
|
#[OAattr\Property(example: 'some example', description: 'some description')]
|
||||||
|
private $property2;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -469,7 +469,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase
|
|||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSame(['property1'], $schema->required, 'should have read constraint in default group');
|
$this->assertSame(['property1'], $schema->required, 'should have read constraint in default group');
|
||||||
$this->assertSame(OA\UNDEFINED, $schema->properties[0]->minimum, 'should not have read constraint in other group');
|
$this->assertSame(Generator::UNDEFINED, $schema->properties[0]->minimum, 'should not have read constraint in other group');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -492,7 +492,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase
|
|||||||
['other']
|
['other']
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSame(OA\UNDEFINED, $schema->required, 'should not have read constraint in default group');
|
$this->assertSame(Generator::UNDEFINED, $schema->required, 'should not have read constraint in default group');
|
||||||
$this->assertSame(1, $schema->properties[0]->minimum, 'should have read constraint in other group');
|
$this->assertSame(1, $schema->properties[0]->minimum, 'should have read constraint in other group');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class GetNelmioAssetTest extends WebTestCase
|
|||||||
{
|
{
|
||||||
static::bootKernel();
|
static::bootKernel();
|
||||||
/** @var GetNelmioAsset $getNelmioAsset */
|
/** @var GetNelmioAsset $getNelmioAsset */
|
||||||
$getNelmioAsset = static::$container->get('nelmio_api_doc.render_docs.html.asset');
|
$getNelmioAsset = static::getContainer()->get('nelmio_api_doc.render_docs.html.asset');
|
||||||
/** @var TwigFunction */
|
/** @var TwigFunction */
|
||||||
$twigFunction = $getNelmioAsset->getFunctions()[0];
|
$twigFunction = $getNelmioAsset->getFunctions()[0];
|
||||||
self::assertSame($expectedContent, $twigFunction->getCallable()->__invoke($mode, $asset));
|
self::assertSame($expectedContent, $twigFunction->getCallable()->__invoke($mode, $asset));
|
||||||
|
@ -157,9 +157,9 @@ class FilteredRouteCollectionBuilderTest extends TestCase
|
|||||||
$this->assertCount(1, $filteredRoutes);
|
$this->assertCount(1, $filteredRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMatchingRoutes(): array
|
public function getMatchingRoutes(): iterable
|
||||||
{
|
{
|
||||||
return [
|
yield from [
|
||||||
['r1', new Route('/api/bar/action1')],
|
['r1', new Route('/api/bar/action1')],
|
||||||
['r2', new Route('/api/foo/action1'), ['path_patterns' => ['^/api', 'i/fo', 'n1$']]],
|
['r2', new Route('/api/foo/action1'), ['path_patterns' => ['^/api', 'i/fo', 'n1$']]],
|
||||||
['r3', new Route('/api/foo/action2'), ['path_patterns' => ['^/api/foo/action2$']]],
|
['r3', new Route('/api/foo/action2'), ['path_patterns' => ['^/api/foo/action2$']]],
|
||||||
@ -167,6 +167,10 @@ class FilteredRouteCollectionBuilderTest extends TestCase
|
|||||||
['r9', new Route('/api/bar/action1', [], [], [], 'api.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.ex']]],
|
['r9', new Route('/api/bar/action1', [], [], [], 'api.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.ex']]],
|
||||||
['r10', new Route('/api/areas/new'), ['path_patterns' => ['^/api']]],
|
['r10', new Route('/api/areas/new'), ['path_patterns' => ['^/api']]],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000) {
|
||||||
|
yield ['r10', new Route('/api/areas_attributes/new'), ['path_patterns' => ['^/api']]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,9 +205,9 @@ class FilteredRouteCollectionBuilderTest extends TestCase
|
|||||||
$this->assertCount(1, $filteredRoutes);
|
$this->assertCount(1, $filteredRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMatchingRoutesWithAnnotation(): array
|
public function getMatchingRoutesWithAnnotation(): iterable
|
||||||
{
|
{
|
||||||
return [
|
yield from [
|
||||||
'with annotation only' => [
|
'with annotation only' => [
|
||||||
'r10',
|
'r10',
|
||||||
new Route('/api/areas/new', ['_controller' => 'ApiController::newAreaAction']),
|
new Route('/api/areas/new', ['_controller' => 'ApiController::newAreaAction']),
|
||||||
@ -215,6 +219,21 @@ class FilteredRouteCollectionBuilderTest extends TestCase
|
|||||||
['path_patterns' => ['^/api'], 'with_annotation' => true],
|
['path_patterns' => ['^/api'], 'with_annotation' => true],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000) {
|
||||||
|
yield from [
|
||||||
|
'with attribute only' => [
|
||||||
|
'r10',
|
||||||
|
new Route('/api/areas_attributes/new', ['_controller' => 'ApiController::newAreaActionAttributes']),
|
||||||
|
['with_annotation' => true],
|
||||||
|
],
|
||||||
|
'with attribute and path patterns' => [
|
||||||
|
'r10',
|
||||||
|
new Route('/api/areas_attributes/new', ['_controller' => 'ApiController::newAreaActionAttributes']),
|
||||||
|
['path_patterns' => ['^/api'], 'with_annotation' => true],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +58,7 @@ class UtilTest extends TestCase
|
|||||||
{
|
{
|
||||||
$context = Util::createContext([], $this->rootContext);
|
$context = Util::createContext([], $this->rootContext);
|
||||||
|
|
||||||
$this->assertSame($this->rootContext, $context->getRootContext());
|
$this->assertContextIsConnectedToRootContext($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateContextWithProperties()
|
public function testCreateContextWithProperties()
|
||||||
@ -818,7 +818,20 @@ class UtilTest extends TestCase
|
|||||||
|
|
||||||
public function assertIsConnectedToRootContext(OA\AbstractAnnotation $annotation)
|
public function assertIsConnectedToRootContext(OA\AbstractAnnotation $annotation)
|
||||||
{
|
{
|
||||||
$this->assertSame($this->rootContext, $annotation->_context->getRootContext());
|
$this->assertContextIsConnectedToRootContext($annotation->_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertContextIsConnectedToRootContext(Context $context)
|
||||||
|
{
|
||||||
|
$getRootContext = \Closure::bind(function (Context $context) use (&$getRootContext) {
|
||||||
|
if (null !== $context->_parent) {
|
||||||
|
return $getRootContext($context->_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $context;
|
||||||
|
}, null, Context::class);
|
||||||
|
|
||||||
|
$this->assertSame($this->rootContext, $getRootContext($context));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSetupPropertiesWithoutClass(array $setup)
|
private function getSetupPropertiesWithoutClass(array $setup)
|
||||||
|
@ -13,14 +13,14 @@ The Upgrade to Swagger 3.0
|
|||||||
The biggest part of the upgrade will most likely be the upgrade of the library `zircote/swagger-php` to `3.0` which introduces new annotations in order to support OpenAPI 3.0 but also changes
|
The biggest part of the upgrade will most likely be the upgrade of the library `zircote/swagger-php` to `3.0` which introduces new annotations in order to support OpenAPI 3.0 but also changes
|
||||||
their namespace from ``Swagger`` to ``OpenApi``.
|
their namespace from ``Swagger`` to ``OpenApi``.
|
||||||
|
|
||||||
They created a dedicated page to help you upgrade : https://zircote.github.io/swagger-php/Migrating-to-v3.html.
|
They created a dedicated page to help you upgrade : https://github.com/zircote/swagger-php/blob/3.x/docs/Migrating-to-v3.md.
|
||||||
|
|
||||||
Here are some additional advices that are more likely to apply to NelmioApiDocBundle users:
|
Here are some additional advices that are more likely to apply to NelmioApiDocBundle users:
|
||||||
|
|
||||||
- Upgrade all your ``use Swagger\Annotations as SWG`` statements to ``use OpenApi\Annotations as OA;`` (to simplify the upgrade you may also stick to the ``SWG`` aliasing).
|
- Upgrade all your ``use Swagger\Annotations as SWG`` statements to ``use OpenApi\Annotations as OA;`` (to simplify the upgrade you may also stick to the ``SWG`` aliasing).
|
||||||
In case you changed ``SWG`` to ``OA``, upgrade all your annotations from ``@SWG\...`` to ``@OA\...``.
|
In case you changed ``SWG`` to ``OA``, upgrade all your annotations from ``@SWG\...`` to ``@OA\...``.
|
||||||
|
|
||||||
- Update your config in case you used inlined swagger docummentation (the field ``nelmio_api_doc.documentation``). [A tool](https://openapi-converter.herokuapp.com/) is available to help you convert it.
|
- Update your config in case you used inlined swagger documentation (the field ``nelmio_api_doc.documentation``). [A tool](https://openapi-converter.herokuapp.com/) is available to help you convert it.
|
||||||
|
|
||||||
- In case you used ``@OA\Response(..., @OA\Schema(...))``, you should explicit your media type by using the annotation ``@OA\JsonContent`` or ``@OA\XmlContent`` instead of ``@OA\Schema``:
|
- In case you used ``@OA\Response(..., @OA\Schema(...))``, you should explicit your media type by using the annotation ``@OA\JsonContent`` or ``@OA\XmlContent`` instead of ``@OA\Schema``:
|
||||||
``@OA\Response(..., @OA\JsonContent(...))`` or ``@OA\Response(..., @OA\XmlContent(...))``.
|
``@OA\Response(..., @OA\JsonContent(...))`` or ``@OA\Response(..., @OA\XmlContent(...))``.
|
||||||
|
@ -15,7 +15,7 @@ trait SetsContextTrait
|
|||||||
// zircote/swagger-php ^3.2
|
// zircote/swagger-php ^3.2
|
||||||
\OpenApi\Analyser::$context = $context;
|
\OpenApi\Analyser::$context = $context;
|
||||||
} else {
|
} else {
|
||||||
/// zircote/swagger-php ^4.0
|
// zircote/swagger-php ^4.0
|
||||||
\OpenApi\Generator::$context = $context;
|
\OpenApi\Generator::$context = $context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,37 +21,37 @@
|
|||||||
"psr/cache": "^1.0|^2.0|^3.0",
|
"psr/cache": "^1.0|^2.0|^3.0",
|
||||||
"psr/container": "^1.0|^2.0",
|
"psr/container": "^1.0|^2.0",
|
||||||
"psr/log": "^1.0|^2.0|^3.0",
|
"psr/log": "^1.0|^2.0|^3.0",
|
||||||
"symfony/config": "^4.4|^5.0",
|
"symfony/config": "^4.4|^5.0|^6.0",
|
||||||
"symfony/console": "^4.4|^5.0",
|
"symfony/console": "^4.4|^5.0|^6.0",
|
||||||
"symfony/dependency-injection": "^4.4|^5.0",
|
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
|
||||||
"symfony/framework-bundle": "^4.4|^5.0",
|
"symfony/framework-bundle": "^4.4|^5.0|^6.0",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^4.4|^5.0|^6.0",
|
||||||
"symfony/http-kernel": "^4.4|^5.0",
|
"symfony/http-kernel": "^4.4|^5.0|^6.0",
|
||||||
"symfony/options-resolver": "^4.4|^5.0",
|
"symfony/options-resolver": "^4.4|^5.0|^6.0",
|
||||||
"symfony/property-info": "^4.4|^5.0",
|
"symfony/property-info": "^4.4|^5.0|^6.0",
|
||||||
"symfony/routing": "^4.4|^5.0",
|
"symfony/routing": "^4.4|^5.0|^6.0",
|
||||||
"zircote/swagger-php": "^3.2|^4.0",
|
"zircote/swagger-php": "^3.2|^4.0",
|
||||||
"phpdocumentor/reflection-docblock": "^3.1|^4.4|^5.0"
|
"phpdocumentor/reflection-docblock": "^3.1|^4.0|^5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"sensio/framework-extra-bundle": "^4.4|^5.0|^6.0",
|
"sensio/framework-extra-bundle": "^4.4|^5.2|^6.0",
|
||||||
"symfony/asset": "^4.4|^5.0",
|
"symfony/asset": "^4.4|^5.2|^6.0",
|
||||||
"symfony/dom-crawler": "^4.4|^5.0",
|
"symfony/dom-crawler": "^4.4|^5.2|^6.0",
|
||||||
"symfony/browser-kit": "^4.4|^5.0",
|
"symfony/browser-kit": "^4.4|^5.2|^6.0",
|
||||||
"symfony/cache": "^4.4|^5.0",
|
"symfony/cache": "^4.4|^5.2|^6.0",
|
||||||
"symfony/form": "^4.4|^5.0",
|
"symfony/form": "^4.4|^5.2|^6.0",
|
||||||
"symfony/phpunit-bridge": "^5.2",
|
"symfony/phpunit-bridge": "^5.2",
|
||||||
"symfony/property-access": "^4.4|^5.0",
|
"symfony/property-access": "^4.4|^5.2|^6.0",
|
||||||
"symfony/serializer": "^4.4|^5.0",
|
"symfony/serializer": "^4.4|^5.2|^6.0",
|
||||||
"symfony/stopwatch": "^4.4|^5.0",
|
"symfony/stopwatch": "^4.4|^5.2|^6.0",
|
||||||
"symfony/templating": "^4.4|^5.0",
|
"symfony/templating": "^4.4|^5.2|^6.0",
|
||||||
"symfony/twig-bundle": "^4.4|^5.0",
|
"symfony/twig-bundle": "^4.4|^5.2|^6.0",
|
||||||
"symfony/validator": "^4.4|^5.0",
|
"symfony/validator": "^4.4|^5.2|^6.0",
|
||||||
|
|
||||||
"api-platform/core": "^2.4",
|
"api-platform/core": "^2.4",
|
||||||
"friendsofsymfony/rest-bundle": "^2.8|^3.0",
|
"friendsofsymfony/rest-bundle": "^2.8|^3.0",
|
||||||
"willdurand/hateoas-bundle": "^1.0|^2.0",
|
"willdurand/hateoas-bundle": "^1.0|^2.0",
|
||||||
"jms/serializer-bundle": "^2.3|^3.0",
|
"jms/serializer-bundle": "^2.3|^3.0|^4.0",
|
||||||
"jms/serializer": "^1.14|^3.0",
|
"jms/serializer": "^1.14|^3.0",
|
||||||
"composer/package-versions-deprecated": "1.11.99.1"
|
"composer/package-versions-deprecated": "1.11.99.1"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user