Migrate annotation ApiDoc to attribute

This commit is contained in:
Ilyas Salikhov 2024-10-01 23:00:23 +03:00
parent 8135dec035
commit a9fceca8df
21 changed files with 375 additions and 602 deletions

View File

@ -1,21 +1,10 @@
<?php
/*
* This file is part of the NelmioApiDocBundle.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Annotation;
namespace Nelmio\ApiDocBundle\Attribute;
use Symfony\Component\Routing\Route;
/**
* @Annotation
*/
#[\Attribute(\Attribute::TARGET_METHOD)]
class ApiDoc
{
public const DEFAULT_VIEW = 'default';
@ -23,158 +12,93 @@ class ApiDoc
/**
* Requirements are mandatory parameters in a route.
*
* @var array
* @var array<string, array<string, string>>
*/
private $requirements = [];
private array $requirements = [];
/**
* Which views is this route used. Defaults to "Default"
*
* @var array
* @var string[]
*/
private $views = [];
private array $views = [];
/**
* Filters are optional parameters in the query string.
*
* @var array
* @var array<string, array<string, string>>
*/
private $filters = [];
private array $filters = [];
/**
* Parameters are data a client can send.
*
* @var array
* @var array<string, array<string, mixed>>
*/
private $parameters = [];
private array $parameters = [];
/**
* Headers that client can send.
*
* @var array
* @var array<string, array<string, mixed>>
*/
private $headers = [];
private array $headers = [];
/**
* @var string
*/
private $input;
/**
* @var string
*/
private $inputs;
/**
* @var string
*/
private $output;
/**
* @var string
*/
private $link;
/**
* Most of the time, a single line of text describing the action.
*
* @var string
*/
private $description;
/**
* Section to group actions together.
*
* @var string
*/
private $section;
private ?string $link = null;
/**
* Extended documentation.
*
* @var string
*/
private $documentation;
private ?string $documentation = null;
private Route $route;
private ?string $host = null;
private string $method;
private string $uri;
private array $response = [];
/**
* @var bool
* @var array<int|string, string[]>
*/
private $resource = false;
private array $statusCodes = [];
/**
* @var string
* @var array<int, array<mixed>>
*/
private $method;
private array $responseMap = [];
private array $parsedResponseMap = [];
/**
* @var string
* @var array<string|int, string>
*/
private $host;
/**
* @var string
*/
private $uri;
/**
* @var array
*/
private $response = [];
/**
* @var Route
*/
private $route;
/**
* @var bool
*/
private $deprecated = false;
/**
* @var array
*/
private $statusCodes = [];
/**
* @var string|null
*/
private $resourceDescription;
/**
* @var array
*/
private $responseMap = [];
/**
* @var array
*/
private $parsedResponseMap = [];
/**
* @var array
*/
private $tags = [];
private array $tags = [];
private ?string $scope = null;
public function __construct(array $data)
{
$this->resource = !empty($data['resource']) ? $data['resource'] : false;
if (isset($data['description'])) {
$this->description = $data['description'];
}
if (isset($data['input'])) {
$this->input = $data['input'];
}
if (isset($data['inputs'])) {
$this->inputs = $data['inputs'];
}
if (isset($data['filters'])) {
foreach ($data['filters'] as $filter) {
/**
* @param string[]|string|null $description
*/
public function __construct(
private string|bool $resource = false,
private array|string|null $description = null,
private string|array|null $input = null,
private ?array $inputs = null,
private string|array|null $output = null,
private ?string $section = null,
private bool $deprecated = false,
private ?string $resourceDescription = null,
?array $filters = null,
?array $requirements = null,
array|string|null $views = null,
?array $parameters = null,
?array $headers = null,
?array $statusCodes = null,
array|string|int|null $tags = null,
?array $responseMap = null,
) {
if (null !== $filters) {
foreach ($filters as $filter) {
if (!isset($filter['name'])) {
throw new \InvalidArgumentException('A "filter" element has to contain a "name" attribute');
}
@ -186,8 +110,8 @@ class ApiDoc
}
}
if (isset($data['requirements'])) {
foreach ($data['requirements'] as $requirement) {
if (null !== $requirements) {
foreach ($requirements as $requirement) {
if (!isset($requirement['name'])) {
throw new \InvalidArgumentException('A "requirement" element has to contain a "name" attribute');
}
@ -199,18 +123,18 @@ class ApiDoc
}
}
if (isset($data['views'])) {
if (!is_array($data['views'])) {
$data['views'] = [$data['views']];
if (null !== $views) {
if (!is_array($views)) {
$views = [$views];
}
foreach ($data['views'] as $view) {
foreach ($views as $view) {
$this->addView($view);
}
}
if (isset($data['parameters'])) {
foreach ($data['parameters'] as $parameter) {
if (null !== $parameters) {
foreach ($parameters as $parameter) {
if (!isset($parameter['name'])) {
throw new \InvalidArgumentException('A "parameter" element has to contain a "name" attribute');
}
@ -229,8 +153,8 @@ class ApiDoc
}
}
if (isset($data['headers'])) {
foreach ($data['headers'] as $header) {
if (null !== $headers) {
foreach ($headers as $header) {
if (!isset($header['name'])) {
throw new \InvalidArgumentException('A "header" element has to contain a "name" attribute');
}
@ -242,27 +166,15 @@ class ApiDoc
}
}
if (isset($data['output'])) {
$this->output = $data['output'];
}
if (isset($data['statusCodes'])) {
foreach ($data['statusCodes'] as $statusCode => $description) {
$this->addStatusCode($statusCode, $description);
if (null !== $statusCodes) {
foreach ($statusCodes as $statusCode => $statusDescription) {
$this->addStatusCode($statusCode, $statusDescription);
}
}
if (isset($data['section'])) {
$this->section = $data['section'];
}
if (isset($data['deprecated'])) {
$this->deprecated = $data['deprecated'];
}
if (isset($data['tags'])) {
if (is_array($data['tags'])) {
foreach ($data['tags'] as $tag => $colorCode) {
if (null !== $tags) {
if (is_array($tags)) {
foreach ($tags as $tag => $colorCode) {
if (is_numeric($tag)) {
$this->addTag($colorCode);
} else {
@ -270,51 +182,34 @@ class ApiDoc
}
}
} else {
$this->tags[] = $data['tags'];
$this->tags[] = $tags;
}
}
if (isset($data['resourceDescription'])) {
$this->resourceDescription = $data['resourceDescription'];
}
if (isset($data['responseMap'])) {
$this->responseMap = $data['responseMap'];
if (null !== $responseMap) {
$this->responseMap = $responseMap;
if (isset($this->responseMap[200])) {
$this->output = $this->responseMap[200];
}
}
}
/**
* @param string $name
*/
public function addFilter($name, array $filter): void
public function addFilter(string $name, array $filter): void
{
$this->filters[$name] = $filter;
}
/**
* @param string $statusCode
*/
public function addStatusCode($statusCode, $description): void
public function addStatusCode(int|string $statusCode, string|array $description): void
{
$this->statusCodes[$statusCode] = !is_array($description) ? [$description] : $description;
}
/**
* @param string $tag
* @param string $colorCode
*/
public function addTag($tag, $colorCode = '#d9534f'): void
public function addTag(int|string $tag, string $colorCode = '#d9534f'): void
{
$this->tags[$tag] = $colorCode;
}
/**
* @param string $name
*/
public function addRequirement($name, array $requirement): void
public function addRequirement(string $name, array $requirement): void
{
$this->requirements[$name] = $requirement;
}
@ -324,119 +219,81 @@ class ApiDoc
$this->requirements = array_merge($this->requirements, $requirements);
}
/**
* @return string|null
*/
public function getInput()
public function getInput(): string|array|null
{
return $this->input;
}
/**
* @return array|null
*/
public function getInputs()
public function getInputs(): ?array
{
return $this->inputs;
}
/**
* @return string|null
*/
public function getOutput()
public function getOutput(): array|string|null
{
return $this->output;
}
/**
* @return string
* @return string[]|string|null
*/
public function getDescription()
public function getDescription(): array|string|null
{
return $this->description;
}
/**
* @param string $description
* @param string[]|string|null $description
*/
public function setDescription($description): void
public function setDescription(array|string|null $description): void
{
$this->description = $description;
}
/**
* @param string $link
*/
public function setLink($link): void
public function setLink(?string $link): void
{
$this->link = $link;
}
/**
* @param string $section
*/
public function setSection($section): void
{
$this->section = $section;
}
/**
* @return string
*/
public function getSection()
public function getSection(): ?string
{
return $this->section;
}
/**
* @return array
*/
public function addView($view)
public function addView(string $view): void
{
$this->views[] = $view;
}
/**
* @return array
* @return string[]
*/
public function getViews()
public function getViews(): array
{
return $this->views;
}
/**
* @param string $documentation
*/
public function setDocumentation($documentation): void
public function setDocumentation(?string $documentation): void
{
$this->documentation = $documentation;
}
/**
* @return string
*/
public function getDocumentation()
public function getDocumentation(): ?string
{
return $this->documentation;
}
/**
* @return bool
*/
public function isResource()
public function isResource(): bool
{
return (bool) $this->resource;
}
public function getResource()
public function getResource(): string|bool
{
return $this->resource && is_string($this->resource) ? $this->resource : false;
}
/**
* @param string $name
*/
public function addParameter($name, array $parameter): void
public function addParameter(string $name, array $parameter): void
{
$this->parameters[$name] = $parameter;
}
@ -480,86 +337,50 @@ class ApiDoc
$this->method = $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY';
}
/**
* @return Route
*/
public function getRoute()
public function getRoute(): Route
{
return $this->route;
}
/**
* @return string
*/
public function getHost()
public function getHost(): ?string
{
return $this->host;
}
/**
* @param string $host
*/
public function setHost($host): void
{
$this->host = $host;
}
/**
* @return bool
*/
public function getDeprecated()
public function getDeprecated(): bool
{
return $this->deprecated;
}
/**
* @return array
* @return array<string, array<string, string>>
*/
public function getFilters()
public function getFilters(): array
{
return $this->filters;
}
/**
* @return array
*/
public function getRequirements()
public function getRequirements(): array
{
return $this->requirements;
}
/**
* @return array
*/
public function getParameters()
public function getParameters(): array
{
return $this->parameters;
}
/**
* @return array
*/
public function getHeaders()
public function getHeaders(): array
{
return $this->headers;
}
/**
* @param bool $deprecated
*
* @return $this
*/
public function setDeprecated($deprecated)
public function setDeprecated(bool $deprecated): void
{
$this->deprecated = (bool) $deprecated;
return $this;
$this->deprecated = $deprecated;
}
/**
* @return string
*/
public function getMethod()
public function getMethod(): string
{
return $this->method;
}
@ -580,8 +401,8 @@ class ApiDoc
public function toArray()
{
$data = [
'method' => $this->method,
'uri' => $this->uri,
'method' => $this->method ?? null,
'uri' => $this->uri ?? null,
];
if ($host = $this->host) {
@ -650,18 +471,12 @@ class ApiDoc
return $data;
}
/**
* @return string|null
*/
public function getResourceDescription()
public function getResourceDescription(): ?string
{
return $this->resourceDescription;
}
/**
* @return array
*/
public function getResponseMap()
public function getResponseMap(): array
{
if (!isset($this->responseMap[200]) && null !== $this->output) {
$this->responseMap[200] = $this->output;
@ -670,21 +485,15 @@ class ApiDoc
return $this->responseMap;
}
/**
* @return array
*/
public function getParsedResponseMap()
public function getParsedResponseMap(): array
{
return $this->parsedResponseMap;
}
/**
* @param int $statusCode
*/
public function setResponseForStatusCode($model, $type, $statusCode = 200): void
public function setResponseForStatusCode(array $model, array $type, int $statusCode = 200): void
{
$this->parsedResponseMap[$statusCode] = ['type' => $type, 'model' => $model];
if (200 == $statusCode && $this->response !== $model) {
if (200 === $statusCode && $this->response !== $model) {
$this->response = $model;
}
}

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Command;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
use Nelmio\ApiDocBundle\Formatter\HtmlFormatter;
use Nelmio\ApiDocBundle\Formatter\MarkdownFormatter;

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
use Nelmio\ApiDocBundle\Formatter\HtmlFormatter;
use Nelmio\ApiDocBundle\Formatter\RequestAwareSwaggerFormatter;

View File

@ -26,7 +26,7 @@ class ExtractorHandlerCompilerPass implements CompilerPassInterface
$container
->getDefinition('nelmio_api_doc.extractor.api_doc_extractor')
->replaceArgument(3, $handlers)
->replaceArgument(2, $handlers)
;
}
}

View File

@ -11,8 +11,7 @@
namespace Nelmio\ApiDocBundle\Extractor;
use Doctrine\Common\Annotations\Reader;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\DataTypes;
use Nelmio\ApiDocBundle\Parser\ParserInterface;
use Nelmio\ApiDocBundle\Parser\PostParserInterface;
@ -22,8 +21,6 @@ use Symfony\Component\Routing\RouterInterface;
class ApiDocExtractor
{
public const ANNOTATION_CLASS = ApiDoc::class;
/**
* @var ParserInterface[]
*/
@ -35,7 +32,6 @@ class ApiDocExtractor
*/
public function __construct(
protected RouterInterface $router,
protected Reader $reader,
protected DocCommentExtractor $commentExtractor,
protected array $handlers,
protected array $excludeSections,
@ -49,17 +45,15 @@ class ApiDocExtractor
*
* @return Route[] An array of routes
*/
public function getRoutes()
public function getRoutes(): array
{
return $this->router->getRouteCollection()->all();
}
/**
/*
* Extracts annotations from all known routes
*
* @return array
*/
public function all($view = ApiDoc::DEFAULT_VIEW)
public function all($view = ApiDoc::DEFAULT_VIEW): array
{
return $this->extractAnnotations($this->getRoutes(), $view);
}
@ -69,10 +63,8 @@ class ApiDocExtractor
*
* @param string $apiVersion API version
* @param string $view
*
* @return array
*/
public function allForVersion($apiVersion, $view = ApiDoc::DEFAULT_VIEW)
public function allForVersion($apiVersion, $view = ApiDoc::DEFAULT_VIEW): array
{
$data = $this->all($view);
foreach ($data as $k => $a) {
@ -94,10 +86,8 @@ class ApiDocExtractor
* - resource
*
* @param array $routes array of Route-objects for which the annotations should be extracted
*
* @return array
*/
public function extractAnnotations(array $routes, $view = ApiDoc::DEFAULT_VIEW)
public function extractAnnotations(array $routes, $view = ApiDoc::DEFAULT_VIEW): array
{
$array = [];
$resources = [];
@ -108,7 +98,7 @@ class ApiDocExtractor
}
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
$annotation = $this->reader->getMethodAnnotation($method, static::ANNOTATION_CLASS);
$annotation = $this->getMethodApiDoc($method);
if (
$annotation && !in_array($annotation->getSection(), $this->excludeSections)
&& (in_array($view, $annotation->getViews()) || (0 === count($annotation->getViews()) && ApiDoc::DEFAULT_VIEW === $view))
@ -214,7 +204,7 @@ class ApiDocExtractor
public function get($controller, $route)
{
if ($method = $this->getReflectionMethod($controller)) {
if ($annotation = $this->reader->getMethodAnnotation($method, static::ANNOTATION_CLASS)) {
if ($annotation = $this->getMethodApiDoc($method)) {
if ($route = $this->router->getRouteCollection()->get($route)) {
return $this->extractData($annotation, $route, $method);
}
@ -224,6 +214,16 @@ class ApiDocExtractor
return null;
}
protected function getMethodApiDoc(\ReflectionMethod $method): ?ApiDoc
{
$attributes = $method->getAttributes(ApiDoc::class, \ReflectionAttribute::IS_INSTANCEOF);
if (!$attributes) {
return null;
}
return $attributes[0]->newInstance();
}
/**
* Registers a class parser to use for parsing input class metadata
*/
@ -444,13 +444,13 @@ class ApiDocExtractor
} elseif (null !== $value) {
if (in_array($name, ['required', 'readonly'])) {
$v1[$name] = $v1[$name] || $value;
} elseif (in_array($name, ['requirement'])) {
} elseif ('requirement' === $name) {
if (isset($v1[$name])) {
$v1[$name] .= ', ' . $value;
} else {
$v1[$name] = $value;
}
} elseif ('default' == $name) {
} elseif ('default' === $name) {
if (isset($v1[$name])) {
$v1[$name] = $value ?? $v1[$name];
} else {
@ -472,8 +472,6 @@ class ApiDocExtractor
/**
* Parses annotations for a given method, and adds new information to the given ApiDoc
* annotation. Useful to extract information from the FOSRestBundle annotations.
*
* @param ReflectionMethod $method
*/
protected function parseAnnotations(ApiDoc $annotation, Route $route, \ReflectionMethod $method): void
{

View File

@ -11,8 +11,7 @@
namespace Nelmio\ApiDocBundle\Extractor;
use Doctrine\Common\Annotations\Reader;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\Resource\FileResource;
@ -32,14 +31,13 @@ class CachingApiDocExtractor extends ApiDocExtractor
*/
public function __construct(
RouterInterface $router,
Reader $reader,
DocCommentExtractor $commentExtractor,
array $handlers,
array $excludeSections,
private string $cacheFile,
private bool $debug = false,
) {
parent::__construct($router, $reader, $commentExtractor, $handlers, $excludeSections);
parent::__construct($router, $commentExtractor, $handlers, $excludeSections);
}
/**
@ -47,15 +45,17 @@ class CachingApiDocExtractor extends ApiDocExtractor
*
* @return array|mixed
*/
public function all($view = ApiDoc::DEFAULT_VIEW)
public function all($view = ApiDoc::DEFAULT_VIEW): array
{
$cache = $this->getViewCache($view);
if (!$cache->isFresh()) {
$resources = [];
foreach ($this->getRoutes() as $route) {
if (null !== ($method = $this->getReflectionMethod($route->getDefault('_controller')))
&& null !== ($annotation = $this->reader->getMethodAnnotation($method, self::ANNOTATION_CLASS))) {
if (
null !== ($method = $this->getReflectionMethod($route->getDefault('_controller')))
&& null !== $this->getMethodApiDoc($method)
) {
$file = $method->getDeclaringClass()->getFileName();
$resources[] = new FileResource($file);
}

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Extractor\Handler;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
use Symfony\Component\Routing\Route;
@ -74,7 +74,7 @@ class PhpDocHandler implements HandlerInterface
$found = false;
foreach ($paramDocs as $paramDoc) {
if (preg_match(sprintf($regexp, preg_quote($var)), $paramDoc, $matches)) {
$annotationRequirements = $annotation->getrequirements();
$annotationRequirements = $annotation->getRequirements();
if (!isset($annotationRequirements[$var]['dataType'])) {
$requirements[$var]['dataType'] = $matches[1] ?? '';

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Extractor;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Symfony\Component\Routing\Route;
interface HandlerInterface

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\DataTypes;
abstract class AbstractFormatter implements FormatterInterface

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
interface FormatterInterface
{

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Symfony\Component\HttpFoundation\Request;
/**

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
class SimpleFormatter extends AbstractFormatter
{

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\DataTypes;
use Nelmio\ApiDocBundle\Swagger\ModelRegistry;
use Symfony\Component\HttpFoundation\Response;

View File

@ -25,7 +25,6 @@
<service id="nelmio_api_doc.extractor.api_doc_extractor" class="%nelmio_api_doc.extractor.api_doc_extractor.class%" public="true">
<argument type="service" id="router" />
<argument type="service" id="annotation_reader" />
<argument type="service" id="nelmio_api_doc.doc_comment_extractor" />
<argument type="collection" />
<argument>%nelmio_api_doc.exclude_sections%</argument>

View File

@ -5,7 +5,7 @@ The bundle provides an ``ApiDoc()`` annotation for your controllers::
namespace Your\Namespace;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
class YourController extends Controller
{

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Tests\Annotation;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Tests\TestCase;
use Symfony\Component\Routing\Route;
@ -19,9 +19,7 @@ class ApiDocTest extends TestCase
{
public function testConstructWithoutData(): void
{
$data = [];
$annot = new ApiDoc($data);
$annot = new ApiDoc();
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -38,12 +36,7 @@ class ApiDocTest extends TestCase
public function testConstructWithInvalidData(): void
{
$data = [
'unknown' => 'foo',
'array' => ['bar' => 'bar'],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc();
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -62,7 +55,7 @@ class ApiDocTest extends TestCase
'description' => 'Heya',
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(description: $data['description']);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -82,7 +75,10 @@ class ApiDocTest extends TestCase
'input' => 'My\Form\Type',
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
description: $data['description'],
input: $data['input']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -104,7 +100,12 @@ class ApiDocTest extends TestCase
'input' => 'My\Form\Type',
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
input: $data['input']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -126,7 +127,12 @@ class ApiDocTest extends TestCase
'input' => 'My\Form\Type',
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
input: $data['input']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -150,7 +156,12 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
filters: $data['filters']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -176,7 +187,10 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
description: $data['description'],
filters: $data['filters']
);
}
public function testConstructWithStatusCodes(): void
@ -193,7 +207,10 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
description: $data['description'],
statusCodes: $data['statusCodes']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -216,7 +233,9 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
requirements: $data['requirements']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -236,7 +255,9 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
parameters: $data['parameters']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -255,7 +276,9 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
headers: $data['headers']
);
$array = $annot->toArray();
$this->assertArrayHasKey('headerName', $array['headers']);
@ -272,7 +295,9 @@ class ApiDocTest extends TestCase
'tags' => 'beta',
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
tags: $data['tags']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -288,7 +313,9 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
tags: $data['tags']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -305,7 +332,9 @@ class ApiDocTest extends TestCase
],
];
$annot = new ApiDoc($data);
$annot = new ApiDoc(
tags: $data['tags']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
@ -322,7 +351,10 @@ class ApiDocTest extends TestCase
],
];
$apiDoc = new ApiDoc($data);
$apiDoc = new ApiDoc(
output: $data['output'],
responseMap: $data['responseMap']
);
$map = $apiDoc->getResponseMap();
@ -341,7 +373,9 @@ class ApiDocTest extends TestCase
],
];
$apiDoc = new ApiDoc($data);
$apiDoc = new ApiDoc(
responseMap: $data['responseMap']
);
$map = $apiDoc->getResponseMap();
$this->assertCount(2, $map);
@ -366,7 +400,7 @@ class ApiDocTest extends TestCase
'{foo}.awesome_host.com'
);
$apiDoc = new ApiDoc([]);
$apiDoc = new ApiDoc();
$apiDoc->setRoute($route);
$this->assertSame($route, $apiDoc->getRoute());

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Tests\Extractor;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
use Nelmio\ApiDocBundle\Tests\WebTestCase;
@ -41,7 +41,7 @@ class ApiDocExtractorTest extends WebTestCase
$this->assertArrayHasKey('annotation', $d);
$this->assertArrayHasKey('resource', $d);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $d['annotation']);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $d['annotation']);
$this->assertInstanceOf('Symfony\Component\Routing\Route', $d['annotation']->getRoute());
$this->assertNotNull($d['resource']);
}
@ -65,7 +65,7 @@ class ApiDocExtractorTest extends WebTestCase
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'test_route_1');
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $annotation);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
$this->assertTrue($annotation->isResource());
$this->assertEquals('index action', $annotation->getDescription());
@ -312,7 +312,7 @@ class ApiDocExtractorTest extends WebTestCase
'test_route_27'
);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $annotation);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
$array = $annotation->toArray();
$this->assertTrue(is_array($array['parameters']));
@ -345,7 +345,7 @@ class ApiDocExtractorTest extends WebTestCase
'test_route_27'
);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $annotation);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
$array = $annotation->toArray();
$this->assertTrue(is_array($array['parameters']));

View File

@ -11,7 +11,7 @@
namespace Nelmio\ApiDocBundle\Tests\Extractor;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\CachingApiDocExtractor;
use Nelmio\ApiDocBundle\Tests\WebTestCase;

View File

@ -11,76 +11,62 @@
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
class ResourceController
{
/**
* @ApiDoc(
* resource=true,
* views={ "test", "premium", "default" },
* resourceDescription="Operations on resource.",
* description="List resources.",
* output="array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test> as tests",
* statusCodes={200 = "Returned on success.", 404 = "Returned if resource cannot be found."}
* )
*/
#[ApiDoc(
resource: true,
views: ['test', 'premium', 'default'],
resourceDescription: 'Operations on resource.',
description: 'List resources.',
output: "array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test> as tests",
statusCodes: [200 => 'Returned on success.', 404 => 'Returned if resource cannot be found.']
)]
public function listResourcesAction(): void
{
}
/**
* @ApiDoc(description="Retrieve a resource by ID.")
*/
#[ApiDoc(description: 'Retrieve a resource by ID.')]
public function getResourceAction(): void
{
}
/**
* @ApiDoc(description="Delete a resource by ID.")
*/
#[ApiDoc(description: 'Delete a resource by ID.')]
public function deleteResourceAction(): void
{
}
/**
* @ApiDoc(
* description="Create a new resource.",
* views={ "default", "premium" },
* input={"class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", "name" = ""},
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested",
* responseMap={
* 400 = {"class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", "form_errors" = true}
* }
* )
*/
#[ApiDoc(
description: 'Create a new resource.',
views: ['default', 'premium'],
input: ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", 'name' => ''],
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested",
responseMap: [
400 => ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", 'form_errors' => true],
]
)]
public function createResourceAction(): void
{
}
/**
* @ApiDoc(
* resource=true,
* views={ "default", "premium" },
* description="List another resource.",
* resourceDescription="Operations on another resource.",
* output="array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest>"
* )
*/
#[ApiDoc(
resource: true,
views: ['default', 'premium'],
description: 'List another resource.',
resourceDescription: 'Operations on another resource.',
output: "array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest>"
)]
public function listAnotherResourcesAction(): void
{
}
/**
* @ApiDoc(description="Retrieve another resource by ID.")
*/
#[ApiDoc(description: 'Retrieve another resource by ID.')]
public function getAnotherResourceAction(): void
{
}
/**
* @ApiDoc(description="Update a resource bu ID.")
*/
#[ApiDoc(description: 'Update a resource bu ID.')]
public function updateAnotherResourceAction(): void
{
}

View File

@ -11,65 +11,57 @@
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Tests\Fixtures\DependencyTypePath;
use Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType;
use Symfony\Component\HttpFoundation\Response;
class TestController
{
/**
* @ApiDoc(
* resource="TestResource",
* views="default"
* )
*/
#[ApiDoc(
resource: 'TestResource',
views: 'default'
)]
public function namedResourceAction(): void
{
}
/**
* @ApiDoc(
* resource=true,
* description="index action",
* filters={
* {"name"="a", "dataType"="integer"},
* {"name"="b", "dataType"="string", "arbitrary"={"arg1", "arg2"}}
* }
* )
*/
#[ApiDoc(
resource: true,
description: 'index action',
filters: [
['name' => 'a', 'dataType' => 'integer'],
['name' => 'b', 'dataType' => 'string', 'arbitrary' => ['arg1', 'arg2']],
],
)]
public function indexAction()
{
return new Response('tests');
}
/**
* @ApiDoc(
* resource=true,
* description="create test",
* views={ "default", "premium" },
* input="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType"
* )
*/
#[ApiDoc(
resource: true,
description: 'create test',
views: ['default', 'premium'],
input: TestType::class
)]
public function postTestAction(): void
{
}
/**
* @ApiDoc(
* description="post test 2",
* views={ "default", "premium" },
* resource=true
* )
*/
#[ApiDoc(
description: 'post test 2',
views: ['default', 'premium'],
resource: true
)]
public function postTest2Action(): void
{
}
/**
* @ApiDoc(
* input="Nelmio\ApiDocBundle\Tests\Fixtures\Form\RequiredType"
* )
*/
#[ApiDoc(
description: 'Action with required parameters',
input: "Nelmio\ApiDocBundle\Tests\Fixtures\Form\RequiredType"
)]
public function requiredParametersAction(): void
{
}
@ -78,16 +70,12 @@ class TestController
{
}
/**
* @ApiDoc()
*/
#[ApiDoc]
public function routeVersionAction(): void
{
}
/**
* @ApiDoc(description="Action without HTTP verb")
*/
#[ApiDoc(description: 'Action without HTTP verb')]
public function anyAction(): void
{
}
@ -96,233 +84,192 @@ class TestController
* This method is useful to test if the getDocComment works.
* And, it supports multilines until the first '@' char.
*
* @ApiDoc()
*
* @param int $id A nice comment
* @param int $page
* @param int $paramType The param type
* @param int $param The param id
*/
#[ApiDoc]
public function myCommentedAction($id, $page, int $paramType, int $param): void
{
}
/**
* @ApiDoc()
*/
#[ApiDoc]
public function yetAnotherAction(): void
{
}
/**
* @ApiDoc(
* views= { "default", "test" },
* description="create another test",
* input=DependencyTypePath::TYPE
* )
*/
#[ApiDoc(
views: ['default', 'test'],
description: 'create another test',
input: DependencyTypePath::TYPE
)]
public function anotherPostAction(): void
{
}
/**
* @ApiDoc(
* description="Testing JMS",
* input="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
* )
*/
#[ApiDoc(
description: 'Testing JMS',
input: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
)]
public function jmsInputTestAction(): void
{
}
/**
* @ApiDoc(
* description="Testing return",
* output=DependencyTypePath::TYPE
* )
*/
#[ApiDoc(
description: 'Testing return',
output: DependencyTypePath::TYPE
)]
public function jmsReturnTestAction(): void
{
}
/**
* @ApiDoc()
*/
#[ApiDoc]
public function zCachedAction(): void
{
}
/**
* @ApiDoc()
*/
#[ApiDoc]
public function zSecuredAction(): void
{
}
/**
* @ApiDoc()
*
* @deprecated
*/
#[ApiDoc]
public function deprecatedAction(): void
{
}
/**
* @ApiDoc(
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
* )
*/
#[ApiDoc(
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
)]
public function jmsReturnNestedOutputAction(): void
{
}
/**
* @ApiDoc(
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsChild"
* )
*/
#[ApiDoc(
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsChild"
)]
public function jmsReturnNestedExtendedOutputAction(): void
{
}
/**
* @ApiDoc(
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest"
* )
*/
#[ApiDoc(
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest"
)]
public function zReturnJmsAndValidationOutputAction(): void
{
}
/**
* @ApiDoc(
* description="Returns a collection of Object",
* requirements={
* {"name"="limit", "dataType"="integer", "requirement"="\d+", "description"="how many objects to return"}
* },
* parameters={
* {"name"="categoryId", "dataType"="integer", "required"=true, "description"="category id"}
* }
* )
*/
#[ApiDoc(
description: 'Returns a collection of Object',
requirements: [
['name' => 'limit', 'dataType' => 'integer', 'requirement' => "\d+", 'description' => 'how many objects to return'],
],
parameters: [
['name' => 'categoryId', 'dataType' => 'integer', 'required' => true, 'description' => 'category id'],
]
)]
public function cgetAction($id): void
{
}
/**
* @ApiDoc(
* input={
* "class"="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType",
* "parsers"={
* "Nelmio\ApiDocBundle\Parser\FormTypeParser",
* }
* }
* )
*/
#[ApiDoc(
input: [
'class' => TestType::class,
'parsers' => ["Nelmio\ApiDocBundle\Parser\FormTypeParser"],
]
)]
public function zReturnSelectedParsersInputAction(): void
{
}
/**
* @ApiDoc(
* output={
* "class"="Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest",
* "parsers"={
* "Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
* "Nelmio\ApiDocBundle\Parser\ValidationParser"
* }
* }
* )
*/
#[ApiDoc(
output: [
'class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest",
'parsers' => [
"Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
"Nelmio\ApiDocBundle\Parser\ValidationParser",
],
]
)]
public function zReturnSelectedParsersOutputAction(): void
{
}
/**
* @ApiDoc(
* section="private"
* )
*/
#[ApiDoc(
section: 'private'
)]
public function privateAction(): void
{
}
/**
* @ApiDoc(
* section="exclusive"
* )
*/
#[ApiDoc(
section: 'exclusive'
)]
public function exclusiveAction(): void
{
}
/**
* @ApiDoc()
*
* @see http://symfony.com
*/
#[ApiDoc]
public function withLinkAction(): void
{
}
/**
* @ApiDoc(
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
* input={
* "class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
* },
* parameters={
* {
* "name"="number",
* "dataType"="integer",
* "actualType"="string",
* "subType"=null,
* "required"=true,
* "description"="This is the new description",
* "readonly"=false,
* "sinceVersion"="v3.0",
* "untilVersion"="v4.0"
* },
* {
* "name"="arr",
* "dataType"="object (ArrayCollection)"
* },
* {
* "name"="nested",
* "dataType"="object (JmsNested)",
* "children": {
* "bar": {
* "dataType"="integer",
* "format"="d+"
* }
* }
* }
* }
* )
*/
#[ApiDoc(
input: ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"],
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
parameters: [
[
'name' => 'number',
'dataType' => 'integer',
'actualType' => 'string',
'subType' => null,
'required' => true,
'description' => 'This is the new description',
'readonly' => false,
'sinceVersion' => 'v3.0',
'untilVersion' => 'v4.0',
],
[
'name' => 'arr',
'dataType' => 'object (ArrayCollection)',
],
[
'name' => 'nested',
'dataType' => 'object (JmsNested)',
'children' => [
'bar' => [
'dataType' => 'integer',
'format' => 'd+',
],
],
],
]
)]
public function overrideJmsAnnotationWithApiDocParametersAction(): void
{
}
/**
* @ApiDoc(
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
* input={
* "class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
* }
* )
*/
#[ApiDoc(
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
input: [
'class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
],
)]
public function defaultJmsAnnotations(): void
{
}
/**
* @ApiDoc(
* description="Route with host placeholder",
* views={ "default" }
* )
*/
#[ApiDoc(
description: 'Route with host placeholder',
views: ['default']
)]
public function routeWithHostAction(): void
{
}

View File

@ -14,4 +14,4 @@ if ((!$loader = includeIfExists(__DIR__ . '/../vendor/autoload.php')) && (!$load
}
// force loading the ApiDoc annotation since the composer target-dir autoloader does not run through $loader::loadClass
class_exists('Nelmio\ApiDocBundle\Annotation\ApiDoc');
class_exists('Nelmio\ApiDocBundle\Attribute\ApiDoc');