diff --git a/Annotation/ApiDoc.php b/Attribute/ApiDoc.php similarity index 54% rename from Annotation/ApiDoc.php rename to Attribute/ApiDoc.php index c18145f2d..625ee1b99 100644 --- a/Annotation/ApiDoc.php +++ b/Attribute/ApiDoc.php @@ -1,21 +1,10 @@ - * - * 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> */ - 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> */ - private $filters = []; + private array $filters = []; /** * Parameters are data a client can send. * - * @var array + * @var array> */ - private $parameters = []; + private array $parameters = []; /** * Headers that client can send. * - * @var array - */ - private $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 + * @var array> */ - private $description; + private array $headers = []; - /** - * Section to group actions together. - * - * @var string - */ - private $section; + private ?string $link = null; /** * Extended documentation. - * - * @var string */ - private $documentation; + private ?string $documentation = null; - /** - * @var bool - */ - private $resource = false; + private Route $route; + private ?string $host = null; + private string $method; + private string $uri; - /** - * @var string - */ - private $method; + private array $response = []; /** - * @var string + * @var array */ - private $host; + private array $statusCodes = []; /** - * @var string + * @var array> */ - private $uri; + private array $responseMap = []; - /** - * @var array - */ - private $response = []; - - /** - * @var Route - */ - private $route; + private array $parsedResponseMap = []; /** - * @var bool + * @var array */ - 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 @@ public function __construct(array $data) } } - 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 @@ public function __construct(array $data) } } - 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 @@ public function __construct(array $data) } } - 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 @@ public function __construct(array $data) } } - 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 @@ public function __construct(array $data) } } } 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 @@ public function setRequirements(array $requirements): void $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 @@ public function setRoute(Route $route): void $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> */ - 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 @@ public function getScope(): ?string 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 @@ public function toArray() 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 @@ public function getResponseMap() 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; } } diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 0237ef9e8..7121ebf21 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -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; diff --git a/Controller/ApiDocController.php b/Controller/ApiDocController.php index 4b8ab8617..cb51530ca 100644 --- a/Controller/ApiDocController.php +++ b/Controller/ApiDocController.php @@ -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; diff --git a/DependencyInjection/ExtractorHandlerCompilerPass.php b/DependencyInjection/ExtractorHandlerCompilerPass.php index ee6aaca22..a02fbec6b 100644 --- a/DependencyInjection/ExtractorHandlerCompilerPass.php +++ b/DependencyInjection/ExtractorHandlerCompilerPass.php @@ -26,7 +26,7 @@ public function process(ContainerBuilder $container): void $container ->getDefinition('nelmio_api_doc.extractor.api_doc_extractor') - ->replaceArgument(3, $handlers) + ->replaceArgument(2, $handlers) ; } } diff --git a/Extractor/ApiDocExtractor.php b/Extractor/ApiDocExtractor.php index 5d59ecd41..b2ebcf4ef 100644 --- a/Extractor/ApiDocExtractor.php +++ b/Extractor/ApiDocExtractor.php @@ -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 @@ 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 @@ public function __construct( * * @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 @@ public function all($view = ApiDoc::DEFAULT_VIEW) * * @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 @@ public function allForVersion($apiVersion, $view = ApiDoc::DEFAULT_VIEW) * - 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 @@ public function extractAnnotations(array $routes, $view = ApiDoc::DEFAULT_VIEW) } 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 @@ public function getReflectionMethod($controller) 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 @@ public function get($controller, $route) 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 @@ protected function mergeParameters($p1, $p2) } 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 @@ protected function mergeParameters($p1, $p2) /** * 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 { diff --git a/Extractor/CachingApiDocExtractor.php b/Extractor/CachingApiDocExtractor.php index e761633ba..d786badca 100644 --- a/Extractor/CachingApiDocExtractor.php +++ b/Extractor/CachingApiDocExtractor.php @@ -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 @@ public function __construct( * * @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); } diff --git a/Extractor/Handler/PhpDocHandler.php b/Extractor/Handler/PhpDocHandler.php index 4e74d79c0..cb9bb89a5 100644 --- a/Extractor/Handler/PhpDocHandler.php +++ b/Extractor/Handler/PhpDocHandler.php @@ -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 @@ public function handle(ApiDoc $annotation, Route $route, \ReflectionMethod $meth $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] ?? ''; diff --git a/Extractor/HandlerInterface.php b/Extractor/HandlerInterface.php index 3605e8c83..c8e1ca71b 100644 --- a/Extractor/HandlerInterface.php +++ b/Extractor/HandlerInterface.php @@ -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 diff --git a/Formatter/AbstractFormatter.php b/Formatter/AbstractFormatter.php index e5dcc882c..0e9eeed33 100644 --- a/Formatter/AbstractFormatter.php +++ b/Formatter/AbstractFormatter.php @@ -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 diff --git a/Formatter/FormatterInterface.php b/Formatter/FormatterInterface.php index 0d043e23f..d4e86adca 100644 --- a/Formatter/FormatterInterface.php +++ b/Formatter/FormatterInterface.php @@ -11,7 +11,7 @@ namespace Nelmio\ApiDocBundle\Formatter; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Nelmio\ApiDocBundle\Attribute\ApiDoc; interface FormatterInterface { diff --git a/Formatter/RequestAwareSwaggerFormatter.php b/Formatter/RequestAwareSwaggerFormatter.php index a5f620ec6..98c254eba 100644 --- a/Formatter/RequestAwareSwaggerFormatter.php +++ b/Formatter/RequestAwareSwaggerFormatter.php @@ -11,7 +11,7 @@ namespace Nelmio\ApiDocBundle\Formatter; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Nelmio\ApiDocBundle\Attribute\ApiDoc; use Symfony\Component\HttpFoundation\Request; /** diff --git a/Formatter/SimpleFormatter.php b/Formatter/SimpleFormatter.php index b6ef4f831..9f84423ad 100644 --- a/Formatter/SimpleFormatter.php +++ b/Formatter/SimpleFormatter.php @@ -11,7 +11,7 @@ namespace Nelmio\ApiDocBundle\Formatter; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Nelmio\ApiDocBundle\Attribute\ApiDoc; class SimpleFormatter extends AbstractFormatter { diff --git a/Formatter/SwaggerFormatter.php b/Formatter/SwaggerFormatter.php index 2f923fbd7..a4b93f7bb 100644 --- a/Formatter/SwaggerFormatter.php +++ b/Formatter/SwaggerFormatter.php @@ -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; diff --git a/Resources/config/services.xml b/Resources/config/services.xml index e79d223c6..25267b77e 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -25,7 +25,6 @@ - %nelmio_api_doc.exclude_sections% diff --git a/Resources/doc/the-apidoc-annotation.rst b/Resources/doc/the-apidoc-annotation.rst index 56e120642..e09a9c011 100644 --- a/Resources/doc/the-apidoc-annotation.rst +++ b/Resources/doc/the-apidoc-annotation.rst @@ -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 { diff --git a/Tests/Annotation/ApiDocTest.php b/Tests/Annotation/ApiDocTest.php index e9bafc320..9a99b0b51 100644 --- a/Tests/Annotation/ApiDocTest.php +++ b/Tests/Annotation/ApiDocTest.php @@ -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 @@ public function testConstructWithoutData(): void 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 @@ public function testConstruct(): void 'description' => 'Heya', ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc(description: $data['description']); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -82,7 +75,10 @@ public function testConstructDefinesAFormType(): void '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 @@ public function testConstructMethodIsResource(): void '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 @@ public function testConstructMethodResourceIsFalse(): void '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 @@ public function testConstructMethodHasFilters(): void ], ]; - $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 @@ public function testConstructMethodHasFiltersWithoutName(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + description: $data['description'], + filters: $data['filters'] + ); } public function testConstructWithStatusCodes(): void @@ -193,7 +207,10 @@ public function testConstructWithStatusCodes(): void ], ]; - $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 @@ public function testConstructWithRequirements(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + requirements: $data['requirements'] + ); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -236,7 +255,9 @@ public function testConstructWithParameters(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + parameters: $data['parameters'] + ); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -255,7 +276,9 @@ public function testConstructWithHeaders(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + headers: $data['headers'] + ); $array = $annot->toArray(); $this->assertArrayHasKey('headerName', $array['headers']); @@ -272,7 +295,9 @@ public function testConstructWithOneTag(): void 'tags' => 'beta', ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + tags: $data['tags'] + ); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -288,7 +313,9 @@ public function testConstructWithOneTagAndColorCode(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + tags: $data['tags'] + ); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -305,7 +332,9 @@ public function testConstructWithMultipleTags(): void ], ]; - $annot = new ApiDoc($data); + $annot = new ApiDoc( + tags: $data['tags'] + ); $array = $annot->toArray(); $this->assertTrue(is_array($array)); @@ -322,7 +351,10 @@ public function testAlignmentOfOutputAndResponseModels(): void ], ]; - $apiDoc = new ApiDoc($data); + $apiDoc = new ApiDoc( + output: $data['output'], + responseMap: $data['responseMap'] + ); $map = $apiDoc->getResponseMap(); @@ -341,7 +373,9 @@ public function testAlignmentOfOutputAndResponseModels2(): void ], ]; - $apiDoc = new ApiDoc($data); + $apiDoc = new ApiDoc( + responseMap: $data['responseMap'] + ); $map = $apiDoc->getResponseMap(); $this->assertCount(2, $map); @@ -366,7 +400,7 @@ public function testSetRoute(): void '{foo}.awesome_host.com' ); - $apiDoc = new ApiDoc([]); + $apiDoc = new ApiDoc(); $apiDoc->setRoute($route); $this->assertSame($route, $apiDoc->getRoute()); diff --git a/Tests/Extractor/ApiDocExtractorTest.php b/Tests/Extractor/ApiDocExtractorTest.php index 4ad3556c9..333df21e6 100644 --- a/Tests/Extractor/ApiDocExtractorTest.php +++ b/Tests/Extractor/ApiDocExtractorTest.php @@ -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 @@ public function testAll(): void $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 @@ public function testGet(): void $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 @@ public function testOverrideJmsAnnotationWithApiDocParameters(): void '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 @@ public function testJmsAnnotation(): void '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'])); diff --git a/Tests/Extractor/CachingApiDocExtractorTest.php b/Tests/Extractor/CachingApiDocExtractorTest.php index 582ad7f4b..ea0e97851 100644 --- a/Tests/Extractor/CachingApiDocExtractorTest.php +++ b/Tests/Extractor/CachingApiDocExtractorTest.php @@ -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; diff --git a/Tests/Fixtures/Controller/ResourceController.php b/Tests/Fixtures/Controller/ResourceController.php index 1b4dbd2c1..f568d0e13 100644 --- a/Tests/Fixtures/Controller/ResourceController.php +++ b/Tests/Fixtures/Controller/ResourceController.php @@ -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 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 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" - * ) - */ + #[ApiDoc( + resource: true, + views: ['default', 'premium'], + description: 'List another resource.', + resourceDescription: 'Operations on another resource.', + output: "array" + )] 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 { } diff --git a/Tests/Fixtures/Controller/TestController.php b/Tests/Fixtures/Controller/TestController.php index ed790a76d..2472179c3 100644 --- a/Tests/Fixtures/Controller/TestController.php +++ b/Tests/Fixtures/Controller/TestController.php @@ -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 @@ public function anotherAction(): void { } - /** - * @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 @@ public function anyAction(): void * 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 { } diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 68b11dca7..c2796bc19 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -14,4 +14,4 @@ function includeIfExists($file) } // 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');