diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ac6c48f14..9cc3fe809 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -34,6 +34,8 @@ jobs: symfony-require: "5.4.*" - php-version: 8.1 symfony-require: "6.3.*" + - php-version: 8.2 + symfony-require: "7.0.*" steps: - name: "Checkout" @@ -59,6 +61,11 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- + - name: Remove packages not compatible symfony 7 + if: matrix.symfony-require == '7.0.*' + run: | + composer remove friendsofsymfony/rest-bundle sensio/framework-extra-bundle jms/serializer-bundle willdurand/hateoas-bundle --no-update --dev + - name: "Install dependencies with composer" env: SYMFONY_REQUIRE: "${{ matrix.symfony-require }}" @@ -66,6 +73,7 @@ jobs: composer global config --no-plugins allow-plugins.symfony/flex true composer global require --no-progress --no-scripts --no-plugins symfony/flex composer update --no-interaction --no-progress ${{ matrix.composer-flags }} + composer update --no-interaction --no-progress ${{ matrix.composer-flags }} - name: PHPUnit Tests run: vendor/bin/simple-phpunit --configuration phpunit.xml.dist --coverage-text diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 3fa9d7ab4..29c78968f 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -62,10 +62,7 @@ protected function configure(): void ; } - /** - * @return int|void - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $area = $input->getOption('area'); $format = $input->getOption('format'); diff --git a/DependencyInjection/Compiler/ConfigurationPass.php b/DependencyInjection/Compiler/ConfigurationPass.php index c2400fb44..df91bddc1 100644 --- a/DependencyInjection/Compiler/ConfigurationPass.php +++ b/DependencyInjection/Compiler/ConfigurationPass.php @@ -14,6 +14,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; /** @@ -29,7 +30,7 @@ public function process(ContainerBuilder $container): void $container->register('nelmio_api_doc.model_describers.form', FormModelDescriber::class) ->setPublic(false) ->addArgument(new Reference('form.factory')) - ->addArgument(new Reference('annotations.reader')) + ->addArgument(new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)) ->addArgument($container->getParameter('nelmio_api_doc.media_types')) ->addArgument($container->getParameter('nelmio_api_doc.use_validation_groups')) ->addTag('nelmio_api_doc.model_describer', ['priority' => 100]); diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index cf6ac501e..edcca5cb7 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -25,6 +25,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -94,7 +95,7 @@ public function load(array $configs, ContainerBuilder $container): void ->setArguments([ new Reference(sprintf('nelmio_api_doc.routes.%s', $area)), new Reference('nelmio_api_doc.controller_reflector'), - new Reference('annotations.reader'), // We cannot use the cached version of the annotation reader since the construction of the annotations is context dependant... + new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // We cannot use the cached version of the annotation reader since the construction of the annotations is context dependant... new Reference('logger'), ]) ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]); @@ -123,7 +124,7 @@ public function load(array $configs, ContainerBuilder $container): void (new Definition(FilteredRouteCollectionBuilder::class)) ->setArguments( [ - new Reference('annotation_reader'), // Here we use the cached version as we don't deal with @OA annotations in this service + new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // Here we use the cached version as we don't deal with @OA annotations in this service new Reference('nelmio_api_doc.controller_reflector'), $area, $areaConfig, @@ -181,7 +182,7 @@ public function load(array $configs, ContainerBuilder $container): void ->setPublic(false) ->setArguments([ new Reference('jms_serializer.metadata_factory'), - new Reference('annotations.reader'), + new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), $config['media_types'], $jmsNamingStrategy, $container->getParameter('nelmio_api_doc.use_validation_groups'), diff --git a/Describer/OpenApiPhpDescriber.php b/Describer/OpenApiPhpDescriber.php index f40e59c95..d6cc5b791 100644 --- a/Describer/OpenApiPhpDescriber.php +++ b/Describer/OpenApiPhpDescriber.php @@ -33,11 +33,15 @@ final class OpenApiPhpDescriber private $routeCollection; private $controllerReflector; + + /** + * @var Reader|null + */ private $annotationReader; private $logger; private $overwrite; - public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, Reader $annotationReader, LoggerInterface $logger, bool $overwrite = false) + public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, ?Reader $annotationReader, LoggerInterface $logger, bool $overwrite = false) { $this->routeCollection = $routeCollection; $this->controllerReflector = $controllerReflector; @@ -51,7 +55,7 @@ public function describe(OA\OpenApi $api) $classAnnotations = []; /** @var \ReflectionMethod $method */ - foreach ($this->getMethodsToParse() as $method => list($path, $httpMethods, $routeName)) { + foreach ($this->getMethodsToParse() as $method => [$path, $httpMethods, $routeName]) { $declaringClass = $method->getDeclaringClass(); $path = Util::getPath($api, $path); @@ -65,16 +69,26 @@ public function describe(OA\OpenApi $api) $this->setContext($context); if (!array_key_exists($declaringClass->getName(), $classAnnotations)) { - $classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) { + $classAnnotations = []; + if (null !== $this->annotationReader) { + $classAnnotations = $this->annotationReader->getClassAnnotations($declaringClass); + } + + $classAnnotations = array_filter($classAnnotations, function ($v) { return $v instanceof OA\AbstractAnnotation; }); + $classAnnotations = array_merge($classAnnotations, $this->getAttributesAsAnnotation($declaringClass, $context)); $classAnnotations[$declaringClass->getName()] = $classAnnotations; } - $annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) { - return $v instanceof OA\AbstractAnnotation; - }); + $annotations = []; + if (null !== $this->annotationReader) { + $annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) { + return $v instanceof OA\AbstractAnnotation; + }); + } + $annotations = array_merge($annotations, $this->getAttributesAsAnnotation($method, $context)); if (0 === count($annotations) && 0 === count($classAnnotations[$declaringClass->getName()])) { diff --git a/ModelDescriber/Annotations/AnnotationsReader.php b/ModelDescriber/Annotations/AnnotationsReader.php index 85f496dcf..b483793ce 100644 --- a/ModelDescriber/Annotations/AnnotationsReader.php +++ b/ModelDescriber/Annotations/AnnotationsReader.php @@ -21,22 +21,16 @@ */ class AnnotationsReader { - private $annotationsReader; - private $modelRegistry; - private $phpDocReader; private $openApiAnnotationsReader; private $symfonyConstraintAnnotationReader; public function __construct( - Reader $annotationsReader, + ?Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes, bool $useValidationGroups = false ) { - $this->annotationsReader = $annotationsReader; - $this->modelRegistry = $modelRegistry; - $this->phpDocReader = new PropertyPhpDocReader(); $this->openApiAnnotationsReader = new OpenApiAnnotationsReader($annotationsReader, $modelRegistry, $mediaTypes); $this->symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader( diff --git a/ModelDescriber/Annotations/OpenApiAnnotationsReader.php b/ModelDescriber/Annotations/OpenApiAnnotationsReader.php index e0f1753ea..026178d2a 100644 --- a/ModelDescriber/Annotations/OpenApiAnnotationsReader.php +++ b/ModelDescriber/Annotations/OpenApiAnnotationsReader.php @@ -28,10 +28,13 @@ class OpenApiAnnotationsReader { use SetsContextTrait; + /** + * @var Reader|null + */ private $annotationsReader; private $modelRegister; - public function __construct(Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes) + public function __construct(?Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes) { $this->annotationsReader = $annotationsReader; $this->modelRegister = new ModelRegister($modelRegistry, $mediaTypes); @@ -97,12 +100,14 @@ private function getAnnotation(Context $parentContext, $reflection, string $clas } } - 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); + if (null !== $this->annotationsReader) { + 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); + } } } finally { $this->setContext(null); diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index aa5701849..ee3b9bb6d 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -28,7 +28,7 @@ class SymfonyConstraintAnnotationReader use SetsContextTrait; /** - * @var Reader + * @var Reader|null */ private $annotationsReader; @@ -42,7 +42,7 @@ class SymfonyConstraintAnnotationReader */ private $useValidationGroups; - public function __construct(Reader $annotationsReader, bool $useValidationGroups=false) + public function __construct(?Reader $annotationsReader, bool $useValidationGroups=false) { $this->annotationsReader = $annotationsReader; $this->useValidationGroups = $useValidationGroups; @@ -215,10 +215,12 @@ private function locateAnnotations($reflection): \Traversable } } - if ($reflection instanceof \ReflectionProperty) { - yield from $this->annotationsReader->getPropertyAnnotations($reflection); - } elseif ($reflection instanceof \ReflectionMethod) { - yield from $this->annotationsReader->getMethodAnnotations($reflection); + if (null !== $this->annotationsReader) { + if ($reflection instanceof \ReflectionProperty) { + yield from $this->annotationsReader->getPropertyAnnotations($reflection); + } elseif ($reflection instanceof \ReflectionMethod) { + yield from $this->annotationsReader->getMethodAnnotations($reflection); + } } } diff --git a/ModelDescriber/FormModelDescriber.php b/ModelDescriber/FormModelDescriber.php index 933d411f4..4a5fd70a2 100644 --- a/ModelDescriber/FormModelDescriber.php +++ b/ModelDescriber/FormModelDescriber.php @@ -40,6 +40,10 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry use SetsContextTrait; private $formFactory; + + /** + * @var Reader|null + */ private $doctrineReader; private $mediaTypes; private $useValidationGroups; @@ -52,9 +56,6 @@ public function __construct( ) { $this->formFactory = $formFactory; $this->doctrineReader = $reader; - if (null === $reader) { - @trigger_error(sprintf('Not passing a doctrine reader to the constructor of %s is deprecated since version 3.8 and won\'t be allowed in version 5.', self::class), E_USER_DEPRECATED); - } if (null === $mediaTypes) { $mediaTypes = ['json']; diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index 991228fd2..6ab63be04 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -40,6 +40,9 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn private $namingStrategy; + /** + * @var Reader|null + */ private $doctrineReader; private $contexts = []; @@ -60,7 +63,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn public function __construct( MetadataFactoryInterface $factory, - Reader $reader, + ?Reader $reader, array $mediaTypes, ?PropertyNamingStrategyInterface $namingStrategy = null, bool $useValidationGroups = false, diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php index 6f81f7b97..6ae19d345 100644 --- a/ModelDescriber/ObjectModelDescriber.php +++ b/ModelDescriber/ObjectModelDescriber.php @@ -35,7 +35,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar private $propertyInfo; /** @var ClassMetadataFactoryInterface|null */ private $classMetadataFactory; - /** @var Reader */ + /** @var Reader|null */ private $doctrineReader; /** @var PropertyDescriberInterface[] */ private $propertyDescribers; @@ -48,7 +48,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar public function __construct( PropertyInfoExtractorInterface $propertyInfo, - Reader $reader, + ?Reader $reader, iterable $propertyDescribers, array $mediaTypes, NameConverterInterface $nameConverter = null, diff --git a/Resources/config/fos_rest.xml b/Resources/config/fos_rest.xml index 32c3045a2..74c99ba82 100644 --- a/Resources/config/fos_rest.xml +++ b/Resources/config/fos_rest.xml @@ -5,7 +5,7 @@ - + diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 0d9801312..9ccd5bad4 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -75,7 +75,7 @@ - + diff --git a/RouteDescriber/FosRestDescriber.php b/RouteDescriber/FosRestDescriber.php index e4c4e1a93..e7bbeeaa9 100644 --- a/RouteDescriber/FosRestDescriber.php +++ b/RouteDescriber/FosRestDescriber.php @@ -27,13 +27,13 @@ final class FosRestDescriber implements RouteDescriberInterface { use RouteDescriberTrait; - /** @var Reader */ + /** @var Reader|null */ private $annotationReader; /** @var string[] */ private $mediaTypes; - public function __construct(Reader $annotationReader, array $mediaTypes) + public function __construct(?Reader $annotationReader, array $mediaTypes) { $this->annotationReader = $annotationReader; $this->mediaTypes = $mediaTypes; @@ -41,7 +41,9 @@ public function __construct(Reader $annotationReader, array $mediaTypes) public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod) { - $annotations = $this->annotationReader->getMethodAnnotations($reflectionMethod); + $annotations = null !== $this->annotationReader + ? $this->annotationReader->getMethodAnnotations($reflectionMethod) + : []; $annotations = array_filter($annotations, static function ($value) { return $value instanceof RequestParam || $value instanceof QueryParam; }); diff --git a/Routing/FilteredRouteCollectionBuilder.php b/Routing/FilteredRouteCollectionBuilder.php index 518603f69..f1fcf5313 100644 --- a/Routing/FilteredRouteCollectionBuilder.php +++ b/Routing/FilteredRouteCollectionBuilder.php @@ -21,7 +21,7 @@ final class FilteredRouteCollectionBuilder { - /** @var Reader */ + /** @var Reader|null */ private $annotationReader; /** @var ControllerReflector */ @@ -34,7 +34,7 @@ final class FilteredRouteCollectionBuilder private $options; public function __construct( - Reader $annotationReader, + ?Reader $annotationReader, ControllerReflector $controllerReflector, string $area, array $options = [] @@ -137,7 +137,7 @@ private function matchAnnotation(Route $route): bool /** @var Areas|null $areas */ $areas = $this->getAttributesAsAnnotation($reflectionMethod->getDeclaringClass(), Areas::class)[0] ?? null; - if (null === $areas) { + if (null === $areas && null !== $this->annotationReader) { /** @var Areas|null $areas */ $areas = $this->annotationReader->getMethodAnnotation( $reflectionMethod, @@ -167,7 +167,10 @@ private function defaultRouteDisabled(Route $route): bool return false; } - $annotations = $this->annotationReader->getMethodAnnotations($method); + $annotations = null !== $this->annotationReader + ? $this->annotationReader->getMethodAnnotations($method) + : []; + if (method_exists(\ReflectionMethod::class, 'getAttributes')) { $annotations = array_merge($annotations, array_map(function (\ReflectionAttribute $attribute) { return $attribute->newInstance(); diff --git a/Tests/Functional/BazingaFunctionalTest.php b/Tests/Functional/BazingaFunctionalTest.php index d7f914609..92df0d95a 100644 --- a/Tests/Functional/BazingaFunctionalTest.php +++ b/Tests/Functional/BazingaFunctionalTest.php @@ -12,12 +12,17 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use Hateoas\Configuration\Embedded; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; class BazingaFunctionalTest extends WebTestCase { protected function setUp(): void { + if (Kernel::MAJOR_VERSION >= 7) { + $this->markTestSkipped('Not supported in symfony 7'); + } + parent::setUp(); static::createClient([], ['HTTP_HOST' => 'api.example.com']); diff --git a/Tests/Functional/Controller/ApiController.php b/Tests/Functional/Controller/ApiController.php index 8985a5098..9b1e87f9a 100644 --- a/Tests/Functional/Controller/ApiController.php +++ b/Tests/Functional/Controller/ApiController.php @@ -13,10 +13,8 @@ use Symfony\Component\Routing\Annotation\Route; -if (\PHP_VERSION_ID >= 80100) { - /** - * @Route("/api", name="api_", host="api.example.com") - */ +if (PHP_VERSION_ID >= 80100) { + #[Route('/api', name: 'api_', host: 'api.example.com')] class ApiController extends ApiController81 { } diff --git a/Tests/Functional/Controller/ApiController80.php b/Tests/Functional/Controller/ApiController80.php index a570ebf53..423aff1fd 100644 --- a/Tests/Functional/Controller/ApiController80.php +++ b/Tests/Functional/Controller/ApiController80.php @@ -39,14 +39,19 @@ 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")) */ @@ -56,14 +61,19 @@ public function fetchArticleAction() /** * @OA\Get( + * * @OA\Response( * response="200", * description="Success", + * * @Model(type=ArticleInterface::class, groups={"light"})) * ) * ) + * * @OA\Parameter(ref="#/components/parameters/test") + * * @Route("/article-interface/{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")) */ @@ -76,15 +86,21 @@ public function fetchArticleInterfaceAction() * * @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)") * ) */ @@ -94,18 +110,24 @@ 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() @@ -114,13 +136,17 @@ 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"})) * ) */ @@ -130,6 +156,7 @@ public function submitUserTypeAction() /** * @Route("/test/{user}", methods={"GET"}, schemes={"https"}, requirements={"user"="/foo/"}) + * * @OA\Response(response=200, description="sucessful") */ public function userAction() @@ -161,6 +188,7 @@ public function adminAction() /** * @OA\Get( * path="/filtered", + * * @OA\Response(response="201", description="") * ) */ @@ -170,10 +198,13 @@ public function filteredAction() /** * @Route("/form", methods={"POST"}) + * * @OA\RequestBody( * description="Request content", + * * @Model(type=DummyType::class)) * ) + * * @OA\Response(response="201", description="") */ public function formAction() @@ -182,10 +213,13 @@ public function formAction() /** * @Route("/form-model", methods={"POST"}) + * * @OA\RequestBody( * description="Request content", + * * @Model(type=FormWithModel::class)) * ) + * * @OA\Response(response="201", description="") */ public function formWithModelAction() @@ -194,7 +228,9 @@ public function formWithModelAction() /** * @Route("/security") + * * @OA\Response(response="201", description="") + * * @Security(name="api_key") * @Security(name="basic") * @Security(name="oauth2", scopes={"scope_1"}) @@ -205,7 +241,9 @@ public function securityAction() /** * @Route("/securityOverride") + * * @OA\Response(response="201", description="") + * * @Security(name="api_key") * @Security(name=null) */ @@ -215,9 +253,11 @@ public function securityActionOverride() /** * @Route("/swagger/symfonyConstraints", methods={"GET"}) + * * @OA\Response( * response="201", * description="Used for symfony constraints test", + * * @Model(type=SymfonyConstraints::class) * ) */ @@ -235,6 +275,7 @@ public function symfonyConstraintsAction() * response="201", * ref="#/components/responses/201" * ) + * * @Route("/configReference", methods={"GET"}) */ public function configReferenceAction() @@ -243,7 +284,9 @@ 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)) @@ -301,7 +344,9 @@ 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() @@ -310,9 +355,11 @@ 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"}) * ) */ @@ -324,6 +371,7 @@ public function symfonyConstraintsWithGroupsAction() * @Route("/alternate-entity-type", methods={"GET", "POST"}) * * @OA\Get(operationId="alternate-entity-type") + * * @OA\Response(response=200, description="success", @OA\JsonContent( * ref=@Model(type=EntityWithAlternateType::class), * )) @@ -336,6 +384,7 @@ public function alternateEntityType() * @Route("/entity-with-ref", methods={"GET", "POST"}) * * @OA\Get(operationId="entity-with-ref") + * * @OA\Response(response=200, description="success", @OA\JsonContent( * ref=@Model(type=EntityWithRef::class), * )) @@ -348,6 +397,7 @@ public function entityWithRef() * @Route("/entity-with-object-type", methods={"GET", "POST"}) * * @OA\Get(operationId="entity-with-object-type") + * * @OA\Response(response=200, description="success", @OA\JsonContent( * ref=@Model(type=EntityWithObjectType::class), * )) @@ -358,11 +408,14 @@ public function entityWithObjectType() /** * @Route("/form-with-alternate-type", methods={"POST"}) + * * @OA\Response( * response="204", * description="Operation automatically detected", * ), + * * @OA\RequestBody( + * * @Model(type=FormWithAlternateSchemaType::class)) * ) */ @@ -372,11 +425,14 @@ public function formWithAlternateSchemaType() /** * @Route("/form-with-ref-type", methods={"POST"}) + * * @OA\Response( * response="204", * description="Operation automatically detected", * ), + * * @OA\RequestBody( + * * @Model(type=FormWithRefType::class)) * ) */ @@ -386,9 +442,11 @@ public function formWithRefSchemaType() /** * @Route("/entity-with-nullable-property-set", methods={"GET"}) + * * @OA\Response( * response="201", * description="Operation automatically detected", + * * @Model(type=EntityWithNullableSchemaSet::class) * ) */ diff --git a/Tests/Functional/Controller/ApiController81.php b/Tests/Functional/Controller/ApiController81.php index b12f5cd06..3e4f36e2a 100644 --- a/Tests/Functional/Controller/ApiController81.php +++ b/Tests/Functional/Controller/ApiController81.php @@ -18,9 +18,365 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article81; use OpenApi\Attributes as OA; use Symfony\Component\Routing\Annotation\Route; +use Nelmio\ApiDocBundle\Annotation\Operation; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\ArticleInterface; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\CompoundEntity; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithAlternateType; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorFileMapping; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; +use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; +use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithAlternateSchemaType; +use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithModel; +use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithRefType; +use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType; -class ApiController81 extends ApiController80 +class ApiController81 { + #[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/{id}', methods: ['GET'])] + #[OA\Parameter(name: 'Accept-Version', in: 'header', schema: new OA\Schema(type: 'string'))] + #[OA\Parameter(name: 'Application-Name', in: 'header', schema: new OA\Schema(type: 'string'))] + public function fetchArticleAction() + { + } + #[OA\Get( + responses: [ + new OA\Response( + response: '200', + description: 'Success', + attachables: [ + new Model(type: ArticleInterface::class, groups: ['light']), + ], + ) + ], + )] + #[OA\Parameter(ref: '#/components/parameters/test')] + #[Route('/article-interface/{id}', methods: ['GET'])] + #[OA\Parameter(name: 'Accept-Version', in: 'header', schema: new OA\Schema(type: 'string'))] + #[OA\Parameter(name: 'Application-Name', in: 'header', schema: new OA\Schema(type: 'string'))] + public function fetchArticleInterfaceAction() + { + } + + #[Route('/swagger', methods: ['GET', 'LINK'])] + #[Route('/swagger2', methods: ['GET'])] + #[Operation([ + 'responses' => [ + new OA\Response( + response: '201', + description: 'An example resource', + ), + ], + ])] + #[OA\Get( + path: '/api/swagger2', + parameters: [ + new OA\Parameter(name: 'Accept-Version', in: 'header', schema: new OA\Schema(type: 'string')), + ], + )] + #[OA\Post( + path: '/api/swagger2', + responses: [ + new 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', + attachables: [ + new Model(type: User::class), + ], + )] + #[OA\RequestBody( + description: 'This is a request body', + content: new OA\JsonContent( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/User'), + ), + )] + #[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', + attachables: [ + new Model(type: User::class), + ], + )] + #[OA\RequestBody( + description: 'This is a request body', + content: new 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. + * + * @deprecated + */ + #[Route('/deprecated', methods: ['GET'])] + 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', + responses: [ + new OA\Response(response: '201', description: ''), + ], + )] + public function filteredAction() + { + } + + #[Route('/form', methods: ['POST'])] + #[OA\RequestBody( + description: 'Request content', + content: new Model(type: DummyType::class), + )] + #[OA\Response(response: 201, description: '')] + public function formAction() + { + } + + #[Route('/form-model', methods: ['POST'])] + #[OA\RequestBody( + description: 'Request content', + content: new Model(type: FormWithModel::class), + )] + #[OA\Response(response: 201, description: '')] + public function formWithModelAction() + { + } + + #[Route('/security', methods: ['GET'])] + #[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', + content: new 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!', attachables: [new 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!', attachables: [new Model(type: CompoundEntity::class)])] + public function compoundEntityAction() + { + } + + #[Route('/discriminator-mapping', methods: ['GET', 'POST'])] + #[OA\Response(response: 200, description: 'Worked well!', attachables: [new Model(type: SymfonyDiscriminator::class)])] + public function discriminatorMappingAction() + { + } + + #[Route('/discriminator-mapping-configured-with-file', methods: ['GET', 'POST'])] + #[OA\Response(response: 200, description: 'Worked well!', attachables: [new Model(type: SymfonyDiscriminatorFileMapping::class)])] + public function discriminatorMappingConfiguredWithFileAction() + { + } + + #[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', + content: new Model(type: SymfonyConstraintsWithValidationGroups::class, groups: ['test']), + )] + public function symfonyConstraintsWithGroupsAction() + { + } + + #[Route('/alternate-entity-type', methods: ['GET', 'POST'])] + #[OA\Get(operationId: 'alternate-entity-type')] + #[OA\Response( + response: 200, + description: 'success', + content: new OA\JsonContent( + ref: new Model(type: EntityWithAlternateType::class), + ), + )] + public function alternateEntityType() + { + } + + #[Route('/entity-with-ref', methods: ['GET', 'POST'])] + #[OA\Get(operationId: 'entity-with-ref')] + #[OA\Response( + response: 200, + description: 'success', + content: new OA\JsonContent( + ref: new Model(type: EntityWithRef::class), + ), + )] + public function entityWithRef() + { + } + + /** + * @Route("/entity-with-object-type", methods={"GET", "POST"}) + * + * @OA\Get(operationId="entity-with-object-type") + * + * @OA\Response(response=200, description="success", @OA\JsonContent( + * ref=@Model(type=EntityWithObjectType::class), + * )) + */ + #[Route('/entity-with-object-type', methods: ['GET', 'POST'])] + #[OA\Get(operationId: 'entity-with-object-type')] + #[OA\Response( + response: 200, + description: 'success', + content: new OA\JsonContent( + ref: new Model(type: EntityWithObjectType::class), + ), + )] + public function entityWithObjectType() + { + } + + #[Route('/form-with-alternate-type', methods: ['POST'])] + #[OA\Response( + response: 204, + description: 'Operation automatically detected', + )] + #[OA\RequestBody( + content: new Model(type: FormWithAlternateSchemaType::class), + )] + public function formWithAlternateSchemaType() + { + } + + #[Route('/form-with-ref-type', methods: ['POST'])] + #[OA\Response( + response: 204, + description: 'Operation automatically detected', + )] + #[OA\RequestBody( + content: new Model(type: FormWithRefType::class), + )] + public function formWithRefSchemaType() + { + } + + #[Route('/entity-with-nullable-property-set', methods: ['GET'])] + #[OA\Response( + response: 201, + description: 'Operation automatically detected', + attachables: [ + new Model(type: EntityWithNullableSchemaSet::class), + ], + )] + public function entityWithNullableSchemaSet() + { + } + #[OA\Get(responses: [ new OA\Response( response: '200', @@ -32,7 +388,7 @@ class ApiController81 extends ApiController80 ])] #[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')])] + #[OA\Parameter(name: 'Accept-Version', in: 'header', schema: new OA\Schema(type: 'string'))] public function fetchArticleActionWithAttributes() { } diff --git a/Tests/Functional/Controller/ArrayItemsErrorController.php b/Tests/Functional/Controller/ArrayItemsErrorController.php index 00c5751d3..29ce11c09 100644 --- a/Tests/Functional/Controller/ArrayItemsErrorController.php +++ b/Tests/Functional/Controller/ArrayItemsErrorController.php @@ -14,22 +14,38 @@ use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Tests\Functional\Entity\ArrayItemsError\Foo; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route(host="api.example.com") - */ -class ArrayItemsErrorController -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @Route("/api/error", methods={"GET"}) - * @OA\Response( - * response=200, - * description="Success", - * @Model(type=Foo::class) - * ) + * @Route(host="api.example.com") */ - public function errorAction() + class ArrayItemsErrorController + { + /** + * @Route("/api/error", methods={"GET"}) + * + * @OA\Response( + * response=200, + * description="Success", + * + * @Model(type=Foo::class) + * ) + */ + public function errorAction() + { + } + } +} else { + #[Route(host: 'api.example.com')] + class ArrayItemsErrorController { + #[Route('/api/error', methods: ['GET'])] + #[OAT\Response(response: 200, description: 'Success', content: new Model(type: Foo::class))] + public function errorAction() + { + } } } diff --git a/Tests/Functional/Controller/BazingaController.php b/Tests/Functional/Controller/BazingaController.php index f8bb7d670..773605cdd 100644 --- a/Tests/Functional/Controller/BazingaController.php +++ b/Tests/Functional/Controller/BazingaController.php @@ -23,9 +23,11 @@ class BazingaController { /** * @Route("/api/bazinga", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=BazingaUser::class) * ) */ @@ -35,9 +37,11 @@ public function userAction() /** * @Route("/api/bazinga_foo", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=BazingaUser::class, groups={"foo"}) * ) */ diff --git a/Tests/Functional/Controller/BazingaTypedController.php b/Tests/Functional/Controller/BazingaTypedController.php index ad72687f3..377721ad4 100644 --- a/Tests/Functional/Controller/BazingaTypedController.php +++ b/Tests/Functional/Controller/BazingaTypedController.php @@ -23,9 +23,11 @@ class BazingaTypedController { /** * @Route("/api/bazinga_typed", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=BazingaUserTyped::class) * ) */ diff --git a/Tests/Functional/Controller/ClassApiController.php b/Tests/Functional/Controller/ClassApiController.php index 21bbd0e45..b85e731b2 100644 --- a/Tests/Functional/Controller/ClassApiController.php +++ b/Tests/Functional/Controller/ClassApiController.php @@ -13,19 +13,36 @@ use Nelmio\ApiDocBundle\Annotation\Security; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/api", host="api.example.com") - * @Security(name="basic") - */ -class ClassApiController -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @Route("/security/class") - * @OA\Response(response="201", description="") + * @Route("/api", host="api.example.com") + * + * @Security(name="basic") */ - public function securityAction() + class ClassApiController + { + /** + * @Route("/security/class") + * + * @OA\Response(response="201", description="") + */ + public function securityAction() + { + } + } +} else { + #[Security(name: 'basic')] + #[Route('/api', host: 'api.example.com')] + class ClassApiController { + #[OAT\Response(response: 201, description: '')] + #[Route('/security/class')] + public function securityAction() + { + } } } diff --git a/Tests/Functional/Controller/FOSRestController.php b/Tests/Functional/Controller/FOSRestController.php index 699198d4a..b9a2ed010 100644 --- a/Tests/Functional/Controller/FOSRestController.php +++ b/Tests/Functional/Controller/FOSRestController.php @@ -14,9 +14,7 @@ use Symfony\Component\Routing\Annotation\Route; if (\PHP_VERSION_ID >= 80100) { - /** - * @Route("/api", host="api.example.com") - */ + #[Route("/api", host: "api.example.com")] class FOSRestController extends FOSRestController81 { } diff --git a/Tests/Functional/Controller/FOSRestController80.php b/Tests/Functional/Controller/FOSRestController80.php index 1e12df917..23352fa8e 100644 --- a/Tests/Functional/Controller/FOSRestController80.php +++ b/Tests/Functional/Controller/FOSRestController80.php @@ -22,8 +22,10 @@ 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")) diff --git a/Tests/Functional/Controller/InvokableController.php b/Tests/Functional/Controller/InvokableController.php index b640b70be..7cecdd472 100644 --- a/Tests/Functional/Controller/InvokableController.php +++ b/Tests/Functional/Controller/InvokableController.php @@ -12,20 +12,37 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * Prevents a regression (see https://github.com/nelmio/NelmioApiDocBundle/issues/1559). - * - * @Route("/api/invoke", host="api.example.com", name="invokable", methods={"GET"}) - * @OA\Response( - * response=200, - * description="Invokable!" - * ) - */ -class InvokableController -{ - public function __invoke() +if (Kernel::MAJOR_VERSION < 7) { + /** + * Prevents a regression (see https://github.com/nelmio/NelmioApiDocBundle/issues/1559). + * + * @Route("/api/invoke", host="api.example.com", name="invokable", methods={"GET"}) + * + * @OA\Response( + * response=200, + * description="Invokable!" + * ) + */ + class InvokableController + { + public function __invoke() + { + } + } +} else { + /** + * Prevents a regression (see https://github.com/nelmio/NelmioApiDocBundle/issues/1559). + */ + #[OAT\Response(response: 200, description: 'Invokable!')] + #[Route('/api/invoke', host: 'api.example.com', name: 'invokable', methods: ['GET'])] + class InvokableController { + public function __invoke() + { + } } } diff --git a/Tests/Functional/Controller/JMSController.php b/Tests/Functional/Controller/JMSController.php index 392eace10..2f377c7aa 100644 --- a/Tests/Functional/Controller/JMSController.php +++ b/Tests/Functional/Controller/JMSController.php @@ -31,9 +31,11 @@ class JMSController { /** * @Route("/api/jms", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSUser::class) * ) */ @@ -43,9 +45,11 @@ public function userAction() /** * @Route("/api/yaml", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=VirtualProperty::class) * ) */ @@ -55,9 +59,11 @@ public function yamlAction() /** * @Route("/api/jms_complex", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSComplex::class, groups={"list", "details", "User" : {"list"}}) * ) */ @@ -67,9 +73,11 @@ public function complexAction() /** * @Route("/api/jms_complex_dual", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSDualComplex::class, groups={"Default", "complex" : {"User" : {"details"}}}) * ) */ @@ -79,9 +87,11 @@ public function complexDualAction() /** * @Route("/api/jms_naming_strategy", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSNamingStrategyConstraints::class, groups={"Default"}) * ) */ @@ -91,9 +101,11 @@ public function namingStrategyConstraintsAction() /** * @Route("/api/jms_chat", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSChat::class, groups={"Default", "members" : {"mini"}}) * ) */ @@ -103,9 +115,11 @@ public function chatAction() /** * @Route("/api/jms_picture", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSPicture::class, groups={"mini"}) * ) */ @@ -115,9 +129,11 @@ public function pictureAction() /** * @Route("/api/jms_mini_user", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSChatUser::class, groups={"mini"}) * ) */ @@ -127,9 +143,11 @@ public function minUserAction() /** * @Route("/api/jms_mini_user_nested", methods={"GET"}) + * * @OA\Response( * response=200, * description="Success", + * * @Model(type=JMSChatRoomUser::class, groups={"mini", "friend": {"living":{"Default"}}}) * ) */ diff --git a/Tests/Functional/Controller/SerializedNameController.php b/Tests/Functional/Controller/SerializedNameController.php index 736a2669f..8a93a258f 100644 --- a/Tests/Functional/Controller/SerializedNameController.php +++ b/Tests/Functional/Controller/SerializedNameController.php @@ -13,25 +13,45 @@ use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded\SerializedNameEnt; +use Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded\Symfony7\SerializedNameEntity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * This controller is only loaded when SerializedName exists (sf >= 4.2). - * - * @Route("/api", host="api.example.com") - */ -class SerializedNameController -{ +if (Kernel::MAJOR_VERSION < 7) { + /** + * This controller is only loaded when SerializedName exists (sf >= 4.2). + * + * @Route("/api", host="api.example.com") + */ + class SerializedNameController + { + /** + * @OA\Response( + * response="200", + * description="success", + * + * @Model(type=SerializedNameEnt::class) + * ) + * + * @Route("/serializename", methods={"GET"}) + */ + public function serializedNameAction() + { + } + } +} else { /** - * @OA\Response( - * response="200", - * description="success", - * @Model(type=SerializedNameEnt::class) - * ) - * @Route("/serializename", methods={"GET"}) + * This controller is only loaded when SerializedName exists (sf >= 4.2). */ - public function serializedNameAction() + #[Route('/api', host: 'api.example.com')] + class SerializedNameController { + #[OAT\Response(response: 200, description: 'success', content: new Model(type: SerializedNameEntity::class))] + #[Route('/serializename', methods: ['GET'])] + public function serializedNameAction() + { + } } } diff --git a/Tests/Functional/Controller/TestController.php b/Tests/Functional/Controller/TestController.php index 7e887e613..0328086d7 100644 --- a/Tests/Functional/Controller/TestController.php +++ b/Tests/Functional/Controller/TestController.php @@ -12,21 +12,36 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/test", host="api-test.example.com") - */ -class TestController -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @OA\Response( - * response="200", - * description="Test" - * ) - * @Route("/test/", methods={"GET"}) + * @Route("/test", host="api-test.example.com") */ - public function testAction() + class TestController + { + /** + * @OA\Response( + * response="200", + * description="Test" + * ) + * + * @Route("/test/", methods={"GET"}) + */ + public function testAction() + { + } + } +} else { + #[Route('/test', host: 'api-test.example.com')] + class TestController { + #[OAT\Response(response: 200, description: 'Test')] + #[Route('/test/', methods: ['GET'])] + public function testAction() + { + } } } diff --git a/Tests/Functional/Controller/UndocumentedController.php b/Tests/Functional/Controller/UndocumentedController.php index 98fbe6562..37bdcdbdc 100644 --- a/Tests/Functional/Controller/UndocumentedController.php +++ b/Tests/Functional/Controller/UndocumentedController.php @@ -11,19 +11,34 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route(host="api.example.com") - */ -class UndocumentedController -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * This path is excluded by the config (only /api allowed). - * - * @Route("/undocumented", methods={"GET"}) + * @Route(host="api.example.com") */ - public function undocumentedAction() + class UndocumentedController + { + /** + * This path is excluded by the config (only /api allowed). + * + * @Route("/undocumented", methods={"GET"}) + */ + public function undocumentedAction() + { + } + } +} else { + #[Route(host: 'api.example.com')] + class UndocumentedController { + /** + * This path is excluded by the config (only /api allowed). + */ + #[Route('/undocumented', methods: ['GET'])] + public function undocumentedAction() + { + } } } diff --git a/Tests/Functional/Entity/Article.php b/Tests/Functional/Entity/Article.php index 922e54620..359ffc828 100644 --- a/Tests/Functional/Entity/Article.php +++ b/Tests/Functional/Entity/Article.php @@ -11,21 +11,36 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Serializer\Annotation\Groups; -/** - * @author Guilhem N. - */ -class Article -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @Groups({"light"}) + * @author Guilhem N. */ - public function setAuthor(User $author) + class Article { - } + /** + * @Groups({"light"}) + */ + public function setAuthor(User $author) + { + } - public function setContent(string $content) + public function setContent(string $content) + { + } + } +} else { + class Article { + #[Groups(['light'])] + public function setAuthor(User $author) + { + } + + public function setContent(string $content) + { + } } } diff --git a/Tests/Functional/Entity/CustomDateTime.php b/Tests/Functional/Entity/CustomDateTime.php index 7f027c538..09af078be 100644 --- a/Tests/Functional/Entity/CustomDateTime.php +++ b/Tests/Functional/Entity/CustomDateTime.php @@ -20,7 +20,9 @@ class CustomDateTime extends \DateTime { /** * @Serializer\Type("string") + * * @Serializer\Expose + * * @Serializer\SerializedName("format") */ private $format; diff --git a/Tests/Functional/Entity/EntityWithAlternateType.php b/Tests/Functional/Entity/EntityWithAlternateType.php index 1b9538c05..1c49ffe5c 100644 --- a/Tests/Functional/Entity/EntityWithAlternateType.php +++ b/Tests/Functional/Entity/EntityWithAlternateType.php @@ -11,25 +11,44 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; -use ArrayIterator; -use IteratorAggregate; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; -/** - * @OA\Schema(type="array", @OA\Items(type="string")) - */ -class EntityWithAlternateType implements IteratorAggregate -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @var string + * @OA\Schema(type="array", @OA\Items(type="string")) */ - public $ignored = 'this property should be ignored because of the annotation above'; + class EntityWithAlternateType implements \IteratorAggregate + { + /** + * @var string + */ + public $ignored = 'this property should be ignored because of the annotation above'; - public function getIterator(): ArrayIterator + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator([ + 'abc', + 'def', + ]); + } + } +} else { + #[OAT\Schema(type: 'array', items: new OAT\Items(type: 'string'))] + class EntityWithAlternateType implements \IteratorAggregate { - return new ArrayIterator([ - 'abc', - 'def', - ]); + /** + * @var string + */ + public $ignored = 'this property should be ignored because of the annotation above'; + + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator([ + 'abc', + 'def', + ]); + } } } diff --git a/Tests/Functional/Entity/EntityWithNullableSchemaSet.php b/Tests/Functional/Entity/EntityWithNullableSchemaSet.php index 615a41f0e..6e8d64a89 100644 --- a/Tests/Functional/Entity/EntityWithNullableSchemaSet.php +++ b/Tests/Functional/Entity/EntityWithNullableSchemaSet.php @@ -12,48 +12,91 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; -class EntityWithNullableSchemaSet -{ - /** - * @var ?string - * - * @OA\Property() - */ - public $nullablePropertyNullableNotSet; - - /** - * @var ?string - * - * @OA\Property(nullable=false) - */ - public $nullablePropertyNullableFalseSet; - - /** - * @var ?string - * - * @OA\Property(nullable=true) - */ - public $nullablePropertyNullableTrueSet; - - /** - * @var string - * - * @OA\Property() - */ - public $nonNullablePropertyNullableNotSet; - - /** - * @var string - * - * @OA\Property(nullable=false) - */ - public $nonNullablePropertyNullableFalseSet; - - /** - * @var string - * - * @OA\Property(nullable=true) - */ - public $nonNullablePropertyNullableTrueSet; +if (Kernel::MAJOR_VERSION < 7) { + class EntityWithNullableSchemaSet + { + /** + * @var ?string + * + * @OA\Property() + */ + public $nullablePropertyNullableNotSet; + + /** + * @var ?string + * + * @OA\Property(nullable=false) + */ + public $nullablePropertyNullableFalseSet; + + /** + * @var ?string + * + * @OA\Property(nullable=true) + */ + public $nullablePropertyNullableTrueSet; + + /** + * @var string + * + * @OA\Property() + */ + public $nonNullablePropertyNullableNotSet; + + /** + * @var string + * + * @OA\Property(nullable=false) + */ + public $nonNullablePropertyNullableFalseSet; + + /** + * @var string + * + * @OA\Property(nullable=true) + */ + public $nonNullablePropertyNullableTrueSet; + } +} else { + class EntityWithNullableSchemaSet + { + /** + * @var ?string + */ + #[OAT\Property] + public $nullablePropertyNullableNotSet; + + /** + * @var ?string + */ + #[OAT\Property(nullable: false)] + public $nullablePropertyNullableFalseSet; + + /** + * @var ?string + */ + #[OAT\Property(nullable: true)] + public $nullablePropertyNullableTrueSet; + + /** + * @var string + */ + #[OAT\Property] + public $nonNullablePropertyNullableNotSet; + + /** + * @var string + */ + #[OAT\Property(nullable: false)] + public $nonNullablePropertyNullableFalseSet; + + /** + * @var string + */ + #[OAT\Property(nullable: true)] + public $nonNullablePropertyNullableTrueSet; + } } diff --git a/Tests/Functional/Entity/EntityWithObjectType.php b/Tests/Functional/Entity/EntityWithObjectType.php index ba0415c23..10b8b2fee 100644 --- a/Tests/Functional/Entity/EntityWithObjectType.php +++ b/Tests/Functional/Entity/EntityWithObjectType.php @@ -12,14 +12,27 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; -/** - * @OA\Schema(type="object") - */ -class EntityWithObjectType -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @var string + * @OA\Schema(type="object") */ - public $notIgnored = 'this should be read'; + class EntityWithObjectType + { + /** + * @var string + */ + public $notIgnored = 'this should be read'; + } +} else { + #[OAT\Schema(type: 'object')] + class EntityWithObjectType + { + /** + * @var string + */ + public $notIgnored = 'this should be read'; + } } diff --git a/Tests/Functional/Entity/EntityWithRef.php b/Tests/Functional/Entity/EntityWithRef.php index f149bfb29..6b04fa592 100644 --- a/Tests/Functional/Entity/EntityWithRef.php +++ b/Tests/Functional/Entity/EntityWithRef.php @@ -12,14 +12,27 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; -/** - * @OA\Schema(ref="#/components/schemas/Test") - */ -class EntityWithRef -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @var string + * @OA\Schema(ref="#/components/schemas/Test") */ - public $ignored = 'this property should be ignored because of the annotation above'; + class EntityWithRef + { + /** + * @var string + */ + public $ignored = 'this property should be ignored because of the annotation above'; + } +} else { + #[OAT\Schema(ref: '#/components/schemas/Test')] + class EntityWithRef + { + /** + * @var string + */ + public $ignored = 'this property should be ignored because of the annotation above'; + } } diff --git a/Tests/Functional/Entity/JMSComplex.php b/Tests/Functional/Entity/JMSComplex.php index 5cfcbba99..fcc7d199f 100644 --- a/Tests/Functional/Entity/JMSComplex.php +++ b/Tests/Functional/Entity/JMSComplex.php @@ -17,8 +17,10 @@ /** * @Serializer\ExclusionPolicy("all") + * * @OA\Schema( * required={"id", "user"}, + * * @OA\Property(property="virtual", ref=@Model(type=JMSUser::class)) * ) */ @@ -26,30 +28,40 @@ class JMSComplex { /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\Groups({"list"}) */ private $id; /** * @OA\Property(ref=@Model(type=JMSUser::class)) + * * @Serializer\Expose + * * @Serializer\Groups({"details"}) + * * @Serializer\SerializedName("user") */ private $User; /** * @Serializer\Type("string") + * * @Serializer\Expose + * * @Serializer\Groups({"list"}) */ private $name; /** * @Serializer\VirtualProperty + * * @Serializer\Expose + * * @Serializer\Groups({"list"}) + * * @OA\Property(ref=@Model(type=JMSUser::class)) */ public function getVirtualFriend() diff --git a/Tests/Functional/Entity/JMSNamingStrategyConstraints.php b/Tests/Functional/Entity/JMSNamingStrategyConstraints.php index 1103e20eb..ca808ed7b 100644 --- a/Tests/Functional/Entity/JMSNamingStrategyConstraints.php +++ b/Tests/Functional/Entity/JMSNamingStrategyConstraints.php @@ -20,10 +20,13 @@ class JMSNamingStrategyConstraints * @var string * * @Serializer\Type("string") + * * @Serializer\SerializedName("beautifulName") * * @Assert\NotBlank() + * * @Assert\Regex(pattern="\w+") + * * @Assert\Length(min="3", max="10") */ private $some_weird_named_property = 'default'; diff --git a/Tests/Functional/Entity/JMSNote.php b/Tests/Functional/Entity/JMSNote.php index c2118f4d8..1ec6abb3c 100644 --- a/Tests/Functional/Entity/JMSNote.php +++ b/Tests/Functional/Entity/JMSNote.php @@ -22,12 +22,14 @@ class JMSNote { /** * @Serializer\Type("string") + * * @Serializer\Expose */ private $long; /** * @Serializer\Type("int") + * * @Serializer\Expose */ private $short; diff --git a/Tests/Functional/Entity/JMSUser.php b/Tests/Functional/Entity/JMSUser.php index 7189ac5c2..438124b78 100644 --- a/Tests/Functional/Entity/JMSUser.php +++ b/Tests/Functional/Entity/JMSUser.php @@ -23,7 +23,9 @@ class JMSUser { /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\Groups({"list"}) * * @OA\Property(description = "User id", readOnly = true, title = "userid", example=1, default = null) @@ -32,7 +34,9 @@ class JMSUser /** * @Serializer\Type("int") + * * @Serializer\Expose + * * @Serializer\SerializedName("daysOnline") * * @OA\Property(default = 0, minimum = 1, maximum = 300) @@ -41,8 +45,11 @@ class JMSUser /** * @Serializer\Type("string") + * * @Serializer\Expose + * * @OA\Property(readOnly = false) + * * @Serializer\Groups({"details"}) */ private $email; @@ -53,7 +60,9 @@ class JMSUser * @var string[] * * @Serializer\Type("array") + * * @Serializer\Accessor(getter="getRoles", setter="setRoles") + * * @Serializer\Expose * * @OA\Property(default = {"user"}, description = "Roles list", example="[""ADMIN"",""SUPERUSER""]", title="roles") @@ -64,6 +73,7 @@ class JMSUser * User Location. * * @Serializer\Type("string") + * * @Serializer\Expose */ private $location; @@ -75,6 +85,7 @@ class JMSUser /** * @OA\Property(property="last_update", type="date") + * * @Serializer\Expose */ private $updatedAt; @@ -88,31 +99,37 @@ class JMSUser /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $friends; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $indexedFriends; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $favoriteDates; /** * @Serializer\Type(Nelmio\ApiDocBundle\Tests\Functional\Entity\CustomDateTime::class) + * * @Serializer\Expose */ private $customDate; /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\SerializedName("friendsNumber") * * @OA\Property(type = "string", minLength = 1, maxLength = 100) @@ -121,6 +138,7 @@ class JMSUser /** * @Serializer\Type(User::class) + * * @Serializer\Expose */ private $bestFriend; @@ -131,7 +149,9 @@ class JMSUser * Only enabled users may be used in actions. * * @var string + * * @Serializer\Type("string") + * * @Serializer\Expose * * @OA\Property(enum = {"disabled", "enabled"}) @@ -142,7 +162,9 @@ class JMSUser * JMS custom types handled via Custom Type Handlers. * * @var string + * * @Serializer\Type("VirtualTypeClassDoesNotExistsHandlerDefined") + * * @Serializer\Expose */ private $virtualType1; @@ -151,50 +173,60 @@ class JMSUser * JMS custom types handled via Custom Type Handlers. * * @var string + * * @Serializer\Type("VirtualTypeClassDoesNotExistsHandlerNotDefined") + * * @Serializer\Expose */ private $virtualType2; /** * @Serializer\Type("array>") + * * @Serializer\Expose */ private $latLonHistory; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $freeFormObject; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $freeFormObjectWithoutType; /** * @Serializer\Type("array>") + * * @Serializer\Expose */ private $deepObject; /** * @Serializer\Type("array>") + * * @Serializer\Expose */ private $deepObjectWithItems; /** * @Serializer\Type("array>>") + * * @Serializer\Expose */ private $deepFreeFormObjectCollection; /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSNote") + * * @Serializer\Inline() + * * @Serializer\Expose */ private $notes; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChat.php b/Tests/Functional/Entity/NestedGroup/JMSChat.php index 5373464fe..a36da193f 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChat.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChat.php @@ -22,12 +22,14 @@ class JMSChat { /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $members; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php b/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php index 161337e79..fd0f7d3f4 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php @@ -22,20 +22,25 @@ class JMSChatFriend { /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatRoom") + * * @Serializer\Expose + * * @Serializer\Groups({"mini"}) */ private $room; /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatLivingRoom") + * * @Serializer\Expose + * * @Serializer\Groups({"Default", "mini"}) */ private $living; /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatRoom") + * * @Serializer\Expose */ private $dining; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatLivingRoom.php b/Tests/Functional/Entity/NestedGroup/JMSChatLivingRoom.php index c47d6df29..307f55f43 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChatLivingRoom.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChatLivingRoom.php @@ -22,6 +22,7 @@ class JMSChatLivingRoom { /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatRoom.php b/Tests/Functional/Entity/NestedGroup/JMSChatRoom.php index 76d4c43fc..f675e79e5 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChatRoom.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChatRoom.php @@ -22,20 +22,25 @@ class JMSChatRoom { /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\Groups({"Default"}) */ private $id1; /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\Groups({"mini"}) */ private $id2; /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id3; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatRoomUser.php b/Tests/Functional/Entity/NestedGroup/JMSChatRoomUser.php index 8aefae3ae..c0b57cf40 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChatRoomUser.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChatRoomUser.php @@ -22,13 +22,16 @@ class JMSChatRoomUser { /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatFriend") + * * @Serializer\Expose + * * @Serializer\Groups({"mini"}) */ private $friend; diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatUser.php b/Tests/Functional/Entity/NestedGroup/JMSChatUser.php index a220bfd3d..d5062f62d 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSChatUser.php +++ b/Tests/Functional/Entity/NestedGroup/JMSChatUser.php @@ -22,19 +22,23 @@ class JMSChatUser { /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; /** * @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture") + * * @Serializer\Groups({"mini"}) + * * @Serializer\Expose */ private $picture; /** * @Serializer\Type("array") + * * @Serializer\Expose */ private $allPictures; diff --git a/Tests/Functional/Entity/NestedGroup/JMSPicture.php b/Tests/Functional/Entity/NestedGroup/JMSPicture.php index 37650bbd6..3a2d70d5c 100644 --- a/Tests/Functional/Entity/NestedGroup/JMSPicture.php +++ b/Tests/Functional/Entity/NestedGroup/JMSPicture.php @@ -22,13 +22,16 @@ class JMSPicture { /** * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; /** * @Serializer\Type("integer") + * * @Serializer\Expose + * * @Serializer\Groups({"mini"}) */ private $onlyDirectPictureMini; diff --git a/Tests/Functional/Entity/SymfonyConstraints.php b/Tests/Functional/Entity/SymfonyConstraints.php index 86e3d1600..7a601a316 100644 --- a/Tests/Functional/Entity/SymfonyConstraints.php +++ b/Tests/Functional/Entity/SymfonyConstraints.php @@ -12,189 +12,361 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Validator\Constraints as Assert; -class SymfonyConstraints -{ - /** - * @var int - * - * @Assert\NotBlank(groups={"test"}) - */ - private $propertyNotBlank; - - /** - * @var int - * - * @Assert\NotNull() - */ - private $propertyNotNull; - - /** - * @var int - * - * @Assert\Length(min="0", max="50") - */ - private $propertyAssertLength; - - /** - * @var int - * - * @Assert\Regex(pattern="/[a-z]{2}/") - */ - private $propertyRegex; - - /** - * @var int - * - * @Assert\Count(min="0", max="10") - */ - private $propertyCount; - - /** - * @var int - * - * @Assert\Choice(choices={"choice1", "choice2"}) - */ - private $propertyChoice; - - /** - * @var int - * - * @Assert\Choice(callback={SymfonyConstraints::class,"fetchAllowedChoices"}) - */ - private $propertyChoiceWithCallback; - - /** - * @var int - * - * @Assert\Choice(callback="fetchAllowedChoices") - */ - private $propertyChoiceWithCallbackWithoutClass; - - /** - * @var string[] - * - * @Assert\Choice(multiple=true, choices={"choice1", "choice2"}) - */ - private $propertyChoiceWithMultiple; - - /** - * @var int - * - * @Assert\Expression( - * "this.getCategory() in ['php', 'symfony'] or !this.isTechnicalPost()", - * message="If this is a tech post, the category should be either php or symfony!" - * ) - */ - private $propertyExpression; - - /** - * @var int - * - * @Assert\Range(min=1, max=5) - */ - private $propertyRange; - - /** - * @var int - * - * @Assert\LessThan(42) - */ - private $propertyLessThan; - - /** - * @var int - * - * @Assert\LessThanOrEqual(23) - */ - private $propertyLessThanOrEqual; - - /** - * @var int - * - * @CustomAssert\CompoundValidationRule() - */ - private $propertyWithCompoundValidationRule; - - public function setPropertyWithCompoundValidationRule(int $propertyWithCompoundValidationRule): void +if (Kernel::MAJOR_VERSION < 7) { + class SymfonyConstraints { - $this->propertyWithCompoundValidationRule = $propertyWithCompoundValidationRule; + /** + * @var int + * + * @Assert\NotBlank(groups={"test"}) + */ + private $propertyNotBlank; + + /** + * @var int + * + * @Assert\NotNull() + */ + private $propertyNotNull; + + /** + * @var int + * + * @Assert\Length(min="0", max="50") + */ + private $propertyAssertLength; + + /** + * @var int + * + * @Assert\Regex(pattern="/[a-z]{2}/") + */ + private $propertyRegex; + + /** + * @var int + * + * @Assert\Count(min="0", max="10") + */ + private $propertyCount; + + /** + * @var int + * + * @Assert\Choice(choices={"choice1", "choice2"}) + */ + private $propertyChoice; + + /** + * @var int + * + * @Assert\Choice(callback={SymfonyConstraints::class,"fetchAllowedChoices"}) + */ + private $propertyChoiceWithCallback; + + /** + * @var int + * + * @Assert\Choice(callback="fetchAllowedChoices") + */ + private $propertyChoiceWithCallbackWithoutClass; + + /** + * @var string[] + * + * @Assert\Choice(multiple=true, choices={"choice1", "choice2"}) + */ + private $propertyChoiceWithMultiple; + + /** + * @var int + * + * @Assert\Expression( + * "this.getCategory() in ['php', 'symfony'] or !this.isTechnicalPost()", + * message="If this is a tech post, the category should be either php or symfony!" + * ) + */ + private $propertyExpression; + + /** + * @var int + * + * @Assert\Range(min=1, max=5) + */ + private $propertyRange; + + /** + * @var int + * + * @Assert\LessThan(42) + */ + private $propertyLessThan; + + /** + * @var int + * + * @Assert\LessThanOrEqual(23) + */ + private $propertyLessThanOrEqual; + + /** + * @var int + * + * @CustomAssert\CompoundValidationRule() + */ + private $propertyWithCompoundValidationRule; + + public function setPropertyWithCompoundValidationRule(int $propertyWithCompoundValidationRule): void + { + $this->propertyWithCompoundValidationRule = $propertyWithCompoundValidationRule; + } + + /** + * @Assert\Count(min="0", max="10") + */ + public function setPropertyNotBlank(int $propertyNotBlank): void + { + $this->propertyNotBlank = $propertyNotBlank; + } + + public function setPropertyNotNull(int $propertyNotNull): void + { + $this->propertyNotNull = $propertyNotNull; + } + + public function setPropertyAssertLength(int $propertyAssertLength): void + { + $this->propertyAssertLength = $propertyAssertLength; + } + + public function setPropertyRegex(int $propertyRegex): void + { + $this->propertyRegex = $propertyRegex; + } + + public function setPropertyCount(int $propertyCount): void + { + $this->propertyCount = $propertyCount; + } + + public function setPropertyChoice(int $propertyChoice): void + { + $this->propertyChoice = $propertyChoice; + } + + public function setPropertyChoiceWithCallback(int $propertyChoiceWithCallback): void + { + $this->propertyChoiceWithCallback = $propertyChoiceWithCallback; + } + + public function setPropertyChoiceWithCallbackWithoutClass(int $propertyChoiceWithCallbackWithoutClass): void + { + $this->propertyChoiceWithCallbackWithoutClass = $propertyChoiceWithCallbackWithoutClass; + } + + public function setPropertyChoiceWithMultiple(array $propertyChoiceWithMultiple): void + { + $this->propertyChoiceWithMultiple = $propertyChoiceWithMultiple; + } + + public function setPropertyExpression(int $propertyExpression): void + { + $this->propertyExpression = $propertyExpression; + } + + public function setPropertyRange(int $propertyRange): void + { + $this->propertyRange = $propertyRange; + } + + public function setPropertyLessThan(int $propertyLessThan): void + { + $this->propertyLessThan = $propertyLessThan; + } + + public function setPropertyLessThanOrEqual(int $propertyLessThanOrEqual): void + { + $this->propertyLessThanOrEqual = $propertyLessThanOrEqual; + } + + /** + * @return array + */ + public static function fetchAllowedChoices() + { + return ['choice1', 'choice2']; + } } - - /** - * @Assert\Count(min="0", max="10") - */ - public function setPropertyNotBlank(int $propertyNotBlank): void - { - $this->propertyNotBlank = $propertyNotBlank; - } - - public function setPropertyNotNull(int $propertyNotNull): void - { - $this->propertyNotNull = $propertyNotNull; - } - - public function setPropertyAssertLength(int $propertyAssertLength): void - { - $this->propertyAssertLength = $propertyAssertLength; - } - - public function setPropertyRegex(int $propertyRegex): void - { - $this->propertyRegex = $propertyRegex; - } - - public function setPropertyCount(int $propertyCount): void - { - $this->propertyCount = $propertyCount; - } - - public function setPropertyChoice(int $propertyChoice): void - { - $this->propertyChoice = $propertyChoice; - } - - public function setPropertyChoiceWithCallback(int $propertyChoiceWithCallback): void - { - $this->propertyChoiceWithCallback = $propertyChoiceWithCallback; - } - - public function setPropertyChoiceWithCallbackWithoutClass(int $propertyChoiceWithCallbackWithoutClass): void - { - $this->propertyChoiceWithCallbackWithoutClass = $propertyChoiceWithCallbackWithoutClass; - } - - public function setPropertyChoiceWithMultiple(array $propertyChoiceWithMultiple): void - { - $this->propertyChoiceWithMultiple = $propertyChoiceWithMultiple; - } - - public function setPropertyExpression(int $propertyExpression): void - { - $this->propertyExpression = $propertyExpression; - } - - public function setPropertyRange(int $propertyRange): void - { - $this->propertyRange = $propertyRange; - } - - public function setPropertyLessThan(int $propertyLessThan): void - { - $this->propertyLessThan = $propertyLessThan; - } - - public function setPropertyLessThanOrEqual(int $propertyLessThanOrEqual): void - { - $this->propertyLessThanOrEqual = $propertyLessThanOrEqual; - } - - /** - * @return array - */ - public static function fetchAllowedChoices() +} else { + class SymfonyConstraints { - return ['choice1', 'choice2']; + /** + * @var int + */ + #[Assert\NotBlank(groups: ['test'])] + private $propertyNotBlank; + + /** + * @var int + */ + #[Assert\NotNull()] + private $propertyNotNull; + + /** + * @var int + */ + #[Assert\Length(min: '0', max: '50')] + private $propertyAssertLength; + + /** + * @var int + */ + #[Assert\Regex(pattern: '/[a-z]{2}/')] + private $propertyRegex; + + /** + * @var int + */ + #[Assert\Count(min: '0', max: '10')] + private $propertyCount; + + /** + * @var int + */ + #[Assert\Choice(choices: ['choice1', 'choice2'])] + private $propertyChoice; + + /** + * @var int + */ + #[Assert\Choice(callback: [self::class, 'fetchAllowedChoices'])] + private $propertyChoiceWithCallback; + + /** + * @var int + */ + #[Assert\Choice(callback: 'fetchAllowedChoices')] + private $propertyChoiceWithCallbackWithoutClass; + + /** + * @var string[] + */ + #[Assert\Choice(multiple: true, choices: ['choice1', 'choice2'])] + private $propertyChoiceWithMultiple; + + /** + * @var int + */ + #[Assert\Expression( + "this.getCategory() in ['php', 'symfony'] or !this.isTechnicalPost()", + message: 'If this is a tech post, the category should be either php or symfony!' + )] + private $propertyExpression; + + /** + * @var int + */ + #[Assert\Range(min: 1, max: 5)] + private $propertyRange; + + /** + * @var int + */ + #[Assert\LessThan(42)] + private $propertyLessThan; + + /** + * @var int + */ + #[Assert\LessThanOrEqual(23)] + private $propertyLessThanOrEqual; + + /** + * @var int + */ + #[CustomAssert\CompoundValidationRule] + private $propertyWithCompoundValidationRule; + + public function setPropertyWithCompoundValidationRule(int $propertyWithCompoundValidationRule): void + { + $this->propertyWithCompoundValidationRule = $propertyWithCompoundValidationRule; + } + + #[Assert\Count(min: '0', max: '10')] + public function setPropertyNotBlank(int $propertyNotBlank): void + { + $this->propertyNotBlank = $propertyNotBlank; + } + + public function setPropertyNotNull(int $propertyNotNull): void + { + $this->propertyNotNull = $propertyNotNull; + } + + public function setPropertyAssertLength(int $propertyAssertLength): void + { + $this->propertyAssertLength = $propertyAssertLength; + } + + public function setPropertyRegex(int $propertyRegex): void + { + $this->propertyRegex = $propertyRegex; + } + + public function setPropertyCount(int $propertyCount): void + { + $this->propertyCount = $propertyCount; + } + + public function setPropertyChoice(int $propertyChoice): void + { + $this->propertyChoice = $propertyChoice; + } + + public function setPropertyChoiceWithCallback(int $propertyChoiceWithCallback): void + { + $this->propertyChoiceWithCallback = $propertyChoiceWithCallback; + } + + public function setPropertyChoiceWithCallbackWithoutClass(int $propertyChoiceWithCallbackWithoutClass): void + { + $this->propertyChoiceWithCallbackWithoutClass = $propertyChoiceWithCallbackWithoutClass; + } + + public function setPropertyChoiceWithMultiple(array $propertyChoiceWithMultiple): void + { + $this->propertyChoiceWithMultiple = $propertyChoiceWithMultiple; + } + + public function setPropertyExpression(int $propertyExpression): void + { + $this->propertyExpression = $propertyExpression; + } + + public function setPropertyRange(int $propertyRange): void + { + $this->propertyRange = $propertyRange; + } + + public function setPropertyLessThan(int $propertyLessThan): void + { + $this->propertyLessThan = $propertyLessThan; + } + + public function setPropertyLessThanOrEqual(int $propertyLessThanOrEqual): void + { + $this->propertyLessThanOrEqual = $propertyLessThanOrEqual; + } + + /** + * @return array + */ + public static function fetchAllowedChoices() + { + return ['choice1', 'choice2']; + } } } diff --git a/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php b/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php index 8a986dd75..f0131e119 100644 --- a/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php +++ b/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php @@ -12,32 +12,63 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; -class SymfonyConstraintsWithValidationGroups -{ - /** - * @var int - * - * @Groups("test") - * @Assert\NotBlank(groups={"test"}) - * @Assert\Range(min=1, max=100) - */ - public $property; - - /** - * @var int - * - * @Assert\Range(min=1, max=100) - */ - public $propertyInDefaultGroup; - - /** - * @var array - * - * @OA\Property(type="array", @OA\Items(type="string")) - * @Assert\Valid - */ - public $propertyArray; +if (Kernel::MAJOR_VERSION < 7) { + class SymfonyConstraintsWithValidationGroups + { + /** + * @var int + * + * @Groups("test") + * + * @Assert\NotBlank(groups={"test"}) + * + * @Assert\Range(min=1, max=100) + */ + public $property; + + /** + * @var int + * + * @Assert\Range(min=1, max=100) + */ + public $propertyInDefaultGroup; + + /** + * @var array + * + * @OA\Property(type="array", @OA\Items(type="string")) + * + * @Assert\Valid + */ + public $propertyArray; + } +} else { + class SymfonyConstraintsWithValidationGroups + { + /** + * @var int + */ + #[Assert\Range(min: 1, max: 100)] + #[Assert\NotBlank(groups: ['test'])] + #[Groups('test')] + public $property; + + /** + * @var int + */ + #[Assert\Range(min: 1, max: 100)] + public $propertyInDefaultGroup; + + /** + * @var array + */ + #[OAT\Property(type: 'array', items: new OAT\Items(type: 'string'))] + #[Assert\Valid] + public $propertyArray; + } } diff --git a/Tests/Functional/Entity/SymfonyDiscriminator.php b/Tests/Functional/Entity/SymfonyDiscriminator.php index cd4e562ad..4341b6159 100644 --- a/Tests/Functional/Entity/SymfonyDiscriminator.php +++ b/Tests/Functional/Entity/SymfonyDiscriminator.php @@ -11,18 +11,33 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -/** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "one": SymfonyDiscriminatorOne::class, - * "two": SymfonyDiscriminatorTwo::class, - * }) - */ -abstract class SymfonyDiscriminator -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @var string + * @DiscriminatorMap(typeProperty="type", mapping={ + * "one": SymfonyDiscriminatorOne::class, + * "two": SymfonyDiscriminatorTwo::class, + * }) */ - public $type; + abstract class SymfonyDiscriminator + { + /** + * @var string + */ + public $type; + } +} else { + #[DiscriminatorMap( + typeProperty: 'type', + mapping: ['one' => SymfonyDiscriminatorOne::class, 'two' => SymfonyDiscriminatorTwo::class] + )] + abstract class SymfonyDiscriminator + { + /** + * @var string + */ + public $type; + } } diff --git a/Tests/Functional/Entity/User.php b/Tests/Functional/Entity/User.php index 041838c43..b06f7f545 100644 --- a/Tests/Functional/Entity/User.php +++ b/Tests/Functional/Entity/User.php @@ -12,157 +12,303 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; +use Symfony\Component\HttpKernel\Kernel; -/** - * @author Guilhem N. - */ -class User -{ +if (Kernel::MAJOR_VERSION < 7) { /** - * @var int - * - * @OA\Property(description = "User id", readOnly = true, title = "userid", default = null) + * @author Guilhem N. */ - private $id; + class User + { + /** + * @var int + * + * @OA\Property(description = "User id", readOnly = true, title = "userid", default = null) + */ + private $id; - /** - * @OA\Property(type="string", readOnly = false) - */ - private $email; + /** + * @OA\Property(type="string", readOnly = false) + */ + private $email; - /** - * User Roles Comment. - * - * @var string[] - * - * @OA\Property( - * description = "User roles", - * title = "roles", - * example="[""ADMIN"",""SUPERUSER""]", - * default = {"user"}, - * ) - */ - private $roles; + /** + * User Roles Comment. + * + * @var string[] + * + * @OA\Property( + * description = "User roles", + * title = "roles", + * example="[""ADMIN"",""SUPERUSER""]", + * default = {"user"}, + * ) + */ + private $roles; - /** - * User Location. - * - * @OA\Property(type = "string") - */ - private $location; + /** + * User Location. + * + * @OA\Property(type = "string") + */ + private $location; - /** - * @var int - * - * @OA\Property(type = "string") - */ - private $friendsNumber; + /** + * @var int + * + * @OA\Property(type = "string") + */ + private $friendsNumber; - /** - * @var float - * @OA\Property(default = 0.0) - */ - private $money; + /** + * @var float + * + * @OA\Property(default = 0.0) + */ + private $money; - /** - * @var \DateTime - * @OA\Property(property="creationDate") - */ - private $createdAt; + /** + * @var \DateTime + * + * @OA\Property(property="creationDate") + */ + private $createdAt; - /** - * @var User[] - */ - private $users; + /** + * @var User[] + */ + private $users; - /** - * @var User|null - */ - private $friend; + /** + * @var User|null + */ + private $friend; - /** - * @var User[]|null - */ - private $friends; + /** + * @var User[]|null + */ + private $friends; - /** - * @var string - * - * @OA\Property(enum = {"disabled", "enabled"}) - */ - private $status; + /** + * @var string + * + * @OA\Property(enum = {"disabled", "enabled"}) + */ + private $status; - /** - * @var \DateTimeInterface - */ - private $dateAsInterface; + /** + * @var \DateTimeInterface + */ + private $dateAsInterface; - public function setMoney(float $money) - { - $this->money = $money; - } + public function setMoney(float $money) + { + $this->money = $money; + } - /** - * @OA\Property(example=1) - */ - public function setId(int $id) - { - $this->id = $id; - } + /** + * @OA\Property(example=1) + */ + public function setId(int $id) + { + $this->id = $id; + } - public function setEmail($email) - { - $this->email = $email; - } + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @param string[] $roles + */ + public function setRoles(array $roles) + { + $this->roles = $roles; + } + + public function setLocation(string $location) + { + } + + public function setFriendsNumber(int $friendsNumber) + { + $this->friendsNumber = $friendsNumber; + } + + public function setCreatedAt(\DateTime $createAt) + { + } + + public function setUsers(array $users) + { + } + + public function setFriend(self $friend = null) + { + } + + public function setFriends(array $friends = []) + { + } + public function setDummy(Dummy $dummy) + { + } + + public function setStatus(string $status) + { + } + + public function getDateAsInterface(): \DateTimeInterface + { + return $this->dateAsInterface; + } + + public function setDateAsInterface(\DateTimeInterface $dateAsInterface) + { + $this->dateAsInterface = $dateAsInterface; + } + } +} else { /** - * @param string[] $roles + * @author Guilhem N. */ - public function setRoles(array $roles) + class User { - $this->roles = $roles; - } + /** + * @var int + */ + #[OAT\Property(description: 'User id', readOnly: true, title: 'userid', default: null)] + private $id; - public function setLocation(string $location) - { - } + #[OAT\Property(type: 'string', readOnly: false)] + private $email; - public function setFriendsNumber(int $friendsNumber) - { - $this->friendsNumber = $friendsNumber; - } + /** + * User Roles Comment. + * + * @var string[] + */ + #[OAT\Property(description: 'User roles', title: 'roles', example: '["ADMIN","SUPERUSER"]', default: ['user'])] + private $roles; - public function setCreatedAt(\DateTime $createAt) - { - } + /** + * User Location. + */ + #[OAT\Property(type: 'string')] + private $location; - public function setUsers(array $users) - { - } + /** + * @var int + */ + #[OAT\Property(type: 'string')] + private $friendsNumber; - public function setFriend(self $friend = null) - { - } + /** + * @var float + */ + #[OAT\Property(default: 0.0)] + private $money; - public function setFriends(array $friends = []) - { - } + /** + * @var \DateTime + */ + #[OAT\Property(property: 'creationDate')] + private $createdAt; - public function setDummy(Dummy $dummy) - { - } + /** + * @var User[] + */ + private $users; - public function setStatus(string $status) - { - } + /** + * @var User|null + */ + private $friend; - public function getDateAsInterface(): \DateTimeInterface - { - return $this->dateAsInterface; - } + /** + * @var User[]|null + */ + private $friends; - public function setDateAsInterface(\DateTimeInterface $dateAsInterface) - { - $this->dateAsInterface = $dateAsInterface; + /** + * @var string + */ + #[OAT\Property(enum: ['disabled', 'enabled'])] + private $status; + + /** + * @var \DateTimeInterface + */ + private $dateAsInterface; + + public function setMoney(float $money) + { + $this->money = $money; + } + + #[OAT\Property(example: 1)] + public function setId(int $id) + { + $this->id = $id; + } + + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @param string[] $roles + */ + public function setRoles(array $roles) + { + $this->roles = $roles; + } + + public function setLocation(string $location) + { + } + + public function setFriendsNumber(int $friendsNumber) + { + $this->friendsNumber = $friendsNumber; + } + + public function setCreatedAt(\DateTime $createAt) + { + } + + public function setUsers(array $users) + { + } + + public function setFriend(self $friend = null) + { + } + + public function setFriends(array $friends = []) + { + } + + public function setDummy(Dummy $dummy) + { + } + + public function setStatus(string $status) + { + } + + public function getDateAsInterface(): \DateTimeInterface + { + return $this->dateAsInterface; + } + + public function setDateAsInterface(\DateTimeInterface $dateAsInterface) + { + $this->dateAsInterface = $dateAsInterface; + } } } diff --git a/Tests/Functional/Entity/VirtualProperty.php b/Tests/Functional/Entity/VirtualProperty.php index 52ab283ef..0f5dfe604 100644 --- a/Tests/Functional/Entity/VirtualProperty.php +++ b/Tests/Functional/Entity/VirtualProperty.php @@ -17,6 +17,7 @@ * Class VirtualProperty. * * @Serializer\ExclusionPolicy("all") + * * @Serializer\VirtualProperty( * "email", * exp="object.user.email", @@ -27,7 +28,9 @@ class VirtualProperty { /** * @var int + * * @Serializer\Type("integer") + * * @Serializer\Expose */ private $id; @@ -39,7 +42,9 @@ class VirtualProperty /** * @Serializer\Accessor(getter="getFoo", setter="setFoo") + * * @Serializer\Type("string") + * * @Serializer\Expose * * Ensures https://github.com/nelmio/NelmioApiDocBundle/issues/1708 is fixed. diff --git a/Tests/Functional/EntityExcluded/ApiPlatform2/Dummy.php b/Tests/Functional/EntityExcluded/ApiPlatform2/Dummy.php index 5b9ce094f..b8c9e6a27 100644 --- a/Tests/Functional/EntityExcluded/ApiPlatform2/Dummy.php +++ b/Tests/Functional/EntityExcluded/ApiPlatform2/Dummy.php @@ -38,6 +38,7 @@ class Dummy * @var string * * @Assert\NotBlank + * * @ApiProperty(iri="http://schema.org/name") */ private $name; diff --git a/Tests/Functional/EntityExcluded/Symfony7/SerializedNameEntity.php b/Tests/Functional/EntityExcluded/Symfony7/SerializedNameEntity.php new file mode 100644 index 000000000..7046b2969 --- /dev/null +++ b/Tests/Functional/EntityExcluded/Symfony7/SerializedNameEntity.php @@ -0,0 +1,34 @@ + + */ +class SerializedNameEntity +{ + /** + * @var string + */ + #[SerializedName('notfoo')] + public $foo; + + /** + * Tests serialized name feature. + */ + #[SerializedName('notwhatyouthink')] + public function setBar(string $bar) + { + } +} diff --git a/Tests/Functional/FOSRestTest.php b/Tests/Functional/FOSRestTest.php index b500512ec..0550c9336 100644 --- a/Tests/Functional/FOSRestTest.php +++ b/Tests/Functional/FOSRestTest.php @@ -13,11 +13,16 @@ use OpenApi\Annotations as OA; use OpenApi\Generator; +use Symfony\Component\HttpKernel\Kernel; class FOSRestTest extends WebTestCase { protected function setUp(): void { + if (Kernel::MAJOR_VERSION >= 7) { + $this->markTestSkipped('Not supported in symfony 7'); + } + parent::setUp(); static::createClient([], ['HTTP_HOST' => 'api.example.com']); diff --git a/Tests/Functional/Form/FormWithAlternateSchemaType.php b/Tests/Functional/Form/FormWithAlternateSchemaType.php index 8dc76c853..644468bd2 100644 --- a/Tests/Functional/Form/FormWithAlternateSchemaType.php +++ b/Tests/Functional/Form/FormWithAlternateSchemaType.php @@ -12,20 +12,35 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Form; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\InputType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -/** - * @OA\Schema(type="string") - */ -class FormWithAlternateSchemaType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void +if (PHP_VERSION_ID < 80100) { + /** + * @OA\Schema(type="string") + */ + class FormWithAlternateSchemaType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('ignored', TextType::class, [ + 'required' => false, + ]); + } + } +} else { + #[OAT\Schema(type: 'string')] + class FormWithAlternateSchemaType extends AbstractType { - $builder - ->add('ignored', InputType::class, [ - 'required' => false, - ]); + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('ignored', TextType::class, [ + 'required' => false, + ]); + } } -} +} \ No newline at end of file diff --git a/Tests/Functional/Form/FormWithRefType.php b/Tests/Functional/Form/FormWithRefType.php index 7b8b89c85..7f998e96a 100644 --- a/Tests/Functional/Form/FormWithRefType.php +++ b/Tests/Functional/Form/FormWithRefType.php @@ -12,20 +12,37 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Form; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\InputType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpKernel\Kernel; -/** - * @OA\Schema(ref="#/components/schemas/Test") - */ -class FormWithRefType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void +if (PHP_VERSION_ID < 80100) { + /** + * @OA\Schema(ref="#/components/schemas/Test") + */ + class FormWithRefType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('ignored', TextType::class, [ + 'required' => false, + ]); + } + } +} else { + #[OAT\Schema(ref: '#/components/schemas/Test')] + class FormWithRefType extends AbstractType { - $builder - ->add('ignored', InputType::class, [ - 'required' => false, - ]); + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('ignored', TextType::class, [ + 'required' => false, + ]); + } } } + diff --git a/Tests/Functional/Form/UserType.php b/Tests/Functional/Form/UserType.php index b7155d0f8..717f0379d 100644 --- a/Tests/Functional/Form/UserType.php +++ b/Tests/Functional/Form/UserType.php @@ -13,51 +13,93 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use OpenApi\Annotations as OA; +use OpenApi\Attributes as OAT; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * @OA\Schema( - * description="this is the description of an user" - * ) - */ -class UserType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void +if (PHP_VERSION_ID < 80100) { + /** + * @OA\Schema( + * description="this is the description of an user" + * ) + */ + class UserType extends AbstractType { - $builder - ->add('strings', CollectionType::class, [ - 'entry_type' => TextType::class, - 'required' => false, - ]) - ->add('dummy', DummyType::class) - ->add('dummies', CollectionType::class, [ - 'entry_type' => DummyType::class, - ]) - ->add('empty_dummies', CollectionType::class, [ - 'entry_type' => DummyEmptyType::class, - 'required' => false, - ]) - ->add('quz', DummyType::class, ['documentation' => ['type' => 'string', 'description' => 'User type.'], 'required' => false]) - ->add('entity', EntityType::class, ['class' => 'Entity']) - ->add('entities', EntityType::class, ['class' => 'Entity', 'multiple' => true]) - ->add('document', DocumentType::class, ['class' => 'Document']) - ->add('documents', DocumentType::class, ['class' => 'Document', 'multiple' => true]) - ->add('extended_builtin', ExtendedBuiltinType::class, ['required_option' => 'foo']) - ->add('hidden', DummyType::class, ['documentation' => false]) - ->add('save', SubmitType::class); - } + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('strings', CollectionType::class, [ + 'entry_type' => TextType::class, + 'required' => false, + ]) + ->add('dummy', DummyType::class) + ->add('dummies', CollectionType::class, [ + 'entry_type' => DummyType::class, + ]) + ->add('empty_dummies', CollectionType::class, [ + 'entry_type' => DummyEmptyType::class, + 'required' => false, + ]) + ->add('quz', DummyType::class, ['documentation' => ['type' => 'string', 'description' => 'User type.'], 'required' => false]) + ->add('entity', EntityType::class, ['class' => 'Entity']) + ->add('entities', EntityType::class, ['class' => 'Entity', 'multiple' => true]) + ->add('document', DocumentType::class, ['class' => 'Document']) + ->add('documents', DocumentType::class, ['class' => 'Document', 'multiple' => true]) + ->add('extended_builtin', ExtendedBuiltinType::class, ['required_option' => 'foo']) + ->add('hidden', DummyType::class, ['documentation' => false]) + ->add('save', SubmitType::class); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); - public function configureOptions(OptionsResolver $resolver): void + $resolver->setRequired('bar'); + } + } +} else { + #[OAT\Schema(description: 'this is the description of an user')] + class UserType extends AbstractType { - $resolver->setDefaults([ - 'data_class' => User::class, - ]); + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('strings', CollectionType::class, [ + 'entry_type' => TextType::class, + 'required' => false, + ]) + ->add('dummy', DummyType::class) + ->add('dummies', CollectionType::class, [ + 'entry_type' => DummyType::class, + ]) + ->add('empty_dummies', CollectionType::class, [ + 'entry_type' => DummyEmptyType::class, + 'required' => false, + ]) + ->add('quz', DummyType::class, ['documentation' => ['type' => 'string', 'description' => 'User type.'], 'required' => false]) + ->add('entity', EntityType::class, ['class' => 'Entity']) + ->add('entities', EntityType::class, ['class' => 'Entity', 'multiple' => true]) + ->add('document', DocumentType::class, ['class' => 'Document']) + ->add('documents', DocumentType::class, ['class' => 'Document', 'multiple' => true]) + ->add('extended_builtin', ExtendedBuiltinType::class, ['required_option' => 'foo']) + ->add('hidden', DummyType::class, ['documentation' => false]) + ->add('save', SubmitType::class); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); - $resolver->setRequired('bar'); + $resolver->setRequired('bar'); + } } } diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 7c9bb784d..9f80d0add 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -11,11 +11,13 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; +use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\Tests\Helper; use OpenApi\Annotations as OAAnnotations; use OpenApi\Attributes as OAAttributes; use OpenApi\Generator; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Serializer\Annotation\SerializedName; class FunctionalTest extends WebTestCase @@ -62,7 +64,9 @@ public function testFetchArticleAction(string $articleRoute) public function provideArticleRoute(): iterable { - yield 'Annotations' => ['/api/article/{id}']; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => ['/api/article/{id}']; + } if (\PHP_VERSION_ID >= 80100) { yield 'Attributes' => ['/api/article_attributes/{id}']; @@ -540,7 +544,12 @@ public function testSerializedNameAction() $this->markTestSkipped('Annotation @SerializedName doesn\'t exist.'); } - $model = $this->getModel('SerializedNameEnt'); + if (Kernel::MAJOR_VERSION >= 7) { + $model = $this->getModel('SerializedNameEntity'); + } else { + $model = $this->getModel('SerializedNameEnt'); + } + $this->assertCount(2, $model->properties); $this->assertNotHasProperty('foo', $model); diff --git a/Tests/Functional/JMSFunctionalTest.php b/Tests/Functional/JMSFunctionalTest.php index 9bcc3c3b4..80c91c9c3 100644 --- a/Tests/Functional/JMSFunctionalTest.php +++ b/Tests/Functional/JMSFunctionalTest.php @@ -11,6 +11,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; class JMSFunctionalTest extends WebTestCase @@ -19,6 +20,10 @@ protected function setUp(): void { parent::setUp(); + if (Kernel::MAJOR_VERSION >= 7) { + $this->markTestSkipped('JMS Serializer is not compatible with Symfony 7+'); + } + static::createClient([], ['HTTP_HOST' => 'api.example.com']); } diff --git a/Tests/Functional/Resources/routes-attributes.yaml b/Tests/Functional/Resources/routes-attributes.yaml new file mode 100644 index 000000000..eab7d3a37 --- /dev/null +++ b/Tests/Functional/Resources/routes-attributes.yaml @@ -0,0 +1,40 @@ +# Resources +test: + resource: ../Controller/TestController.php + type: attribute + +api: + resource: ../Controller/ApiController.php + type: attribute + +class_api: + resource: ../Controller/ClassApiController.php + type: attribute + +undocumented: + resource: ../Controller/UndocumentedController.php + type: attribute + +invokable: + resource: ../Controller/InvokableController.php + type: attribute + +api_platform: + resource: . + prefix: /api + type: api_platform + +# Controllers +doc_area: + path: /{area}/docs + controller: nelmio_api_doc.controller.swagger_ui + defaults: + area: default + +doc_json: + path: /{area}/docs.json + controller: nelmio_api_doc.controller.swagger_json + +doc_yaml: + path: /{area}/docs.yaml + controller: nelmio_api_doc.controller.swagger_yaml \ No newline at end of file diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index d2aa13978..979e9c4bb 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -37,15 +37,16 @@ class TestKernel extends Kernel { - const USE_JMS = 1; - const USE_BAZINGA = 2; - const ERROR_ARRAY_ITEMS = 4; - const USE_VALIDATION_GROUPS = 8; - use MicroKernelTrait; + public const USE_JMS = 1; + public const USE_BAZINGA = 2; + public const ERROR_ARRAY_ITEMS = 4; + public const USE_VALIDATION_GROUPS = 8; private $flags; + private bool $supportsLegacyAnnotations; + public function __construct(int $flags = 0) { parent::__construct('test'.$flags, true); @@ -53,59 +54,62 @@ public function __construct(int $flags = 0) $this->flags = $flags; } - /** - * {@inheritdoc} - */ public function registerBundles(): iterable { $bundles = [ new FrameworkBundle(), new TwigBundle(), - new SensioFrameworkExtraBundle(), new ApiPlatformBundle(), new NelmioApiDocBundle(), new TestBundle(), - new FOSRestBundle(), ]; - if ($this->flags & self::USE_JMS) { - $bundles[] = new JMSSerializerBundle(); + if (self::MAJOR_VERSION < 7) { + $bundles[] = new SensioFrameworkExtraBundle(); + $bundles[] = new FOSRestBundle(); - if ($this->flags & self::USE_BAZINGA) { - $bundles[] = new BazingaHateoasBundle(); + if ($this->flags & self::USE_JMS) { + $bundles[] = new JMSSerializerBundle(); + + if ($this->flags & self::USE_BAZINGA) { + $bundles[] = new BazingaHateoasBundle(); + } } } return $bundles; } - /** - * {@inheritdoc} - */ protected function configureRoutes($routes) { - $this->import($routes, __DIR__.'/Resources/routes.yaml', '/', 'yaml'); + if (self::MAJOR_VERSION < 7) { + $this->import($routes, __DIR__.'/Resources/routes.yaml', '/', 'yaml'); + } else { + $this->import($routes, __DIR__.'/Resources/routes-attributes.yaml', '/', 'yaml'); + } if (class_exists(SerializedName::class)) { - $this->import($routes, __DIR__.'/Controller/SerializedNameController.php', '/', 'annotation'); + $this->import($routes, __DIR__.'/Controller/SerializedNameController.php', '/', self::MAJOR_VERSION < 7 ? 'annotation' : 'attribute'); } - if ($this->flags & self::USE_JMS) { - $this->import($routes, __DIR__.'/Controller/JMSController.php', '/', 'annotation'); + if ($this->flags & self::ERROR_ARRAY_ITEMS) { + $this->import($routes, __DIR__.'/Controller/ArrayItemsErrorController.php', '/', self::MAJOR_VERSION < 7 ? 'annotation' : 'attribute'); } - if ($this->flags & self::USE_BAZINGA) { - $this->import($routes, __DIR__.'/Controller/BazingaController.php', '/', 'annotation'); - - try { - new \ReflectionMethod(Embedded::class, 'getType'); - $this->import($routes, __DIR__.'/Controller/BazingaTypedController.php', '/', 'annotation'); - } catch (\ReflectionException $e) { + if (self::MAJOR_VERSION < 7) { + if ($this->flags & self::USE_JMS) { + $this->import($routes, __DIR__.'/Controller/JMSController.php', '/', 'annotation'); } - } - if ($this->flags & self::ERROR_ARRAY_ITEMS) { - $this->import($routes, __DIR__.'/Controller/ArrayItemsErrorController.php', '/', 'annotation'); + if ($this->flags & self::USE_BAZINGA) { + $this->import($routes, __DIR__.'/Controller/BazingaController.php', '/', 'annotation'); + + try { + new \ReflectionMethod(Embedded::class, 'getType'); + $this->import($routes, __DIR__.'/Controller/BazingaTypedController.php', '/', 'annotation'); + } catch (\ReflectionException $e) { + } + } } } @@ -121,9 +125,6 @@ private function import($routes, $resource, $prefix, $type) } } - /** - * {@inheritdoc} - */ protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $framework = [ @@ -132,8 +133,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'test' => null, 'validation' => null, 'form' => null, - 'serializer' => [ - 'enable_annotations' => true, + 'serializer' => self::MAJOR_VERSION < 7 ? ['enable_annotations' => true] : [] + [ 'mapping' => [ 'paths' => [__DIR__.'/Resources/serializer/'], ], @@ -157,11 +157,13 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'exception_controller' => null, ]); - $c->loadFromExtension('sensio_framework_extra', [ - 'router' => [ - 'annotations' => false, - ], - ]); + if (self::MAJOR_VERSION < 7) { + $c->loadFromExtension('sensio_framework_extra', [ + 'router' => [ + 'annotations' => false, + ], + ]); + } $c->loadFromExtension('api_platform', [ 'mapping' => ['paths' => [ @@ -171,28 +173,77 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ]], ]); - $c->loadFromExtension('fos_rest', [ - 'format_listener' => [ - 'rules' => [ - [ - 'path' => '^/', - 'fallback_format' => 'json', + if (self::MAJOR_VERSION < 7) { + $c->loadFromExtension('fos_rest', [ + 'format_listener' => [ + 'rules' => [ + [ + 'path' => '^/', + 'fallback_format' => 'json', + ], ], ], + ]); + + // If FOSRestBundle 2.8 + if (class_exists(\FOS\RestBundle\EventListener\ResponseStatusCodeListener::class)) { + $c->loadFromExtension('fos_rest', [ + 'exception' => [ + 'enabled' => false, + 'exception_listener' => false, + 'serialize_exceptions' => false, + ], + 'body_listener' => false, + 'routing_loader' => false, + ]); + } + } + + $models = [ + [ + 'alias' => 'PrivateProtectedExposure', + 'type' => PrivateProtectedExposure::class, ], - ]); + [ + 'alias' => 'SymfonyConstraintsTestGroup', + 'type' => SymfonyConstraintsWithValidationGroups::class, + 'groups' => ['test'], + ], + [ + 'alias' => 'SymfonyConstraintsDefaultGroup', + 'type' => SymfonyConstraintsWithValidationGroups::class, + 'groups' => null, + ], + ]; - // If FOSRestBundle 2.8 - if (class_exists(\FOS\RestBundle\EventListener\ResponseStatusCodeListener::class)) { - $c->loadFromExtension('fos_rest', [ - 'exception' => [ - 'enabled' => false, - 'exception_listener' => false, - 'serialize_exceptions' => false, + if (self::MAJOR_VERSION < 7) { + $models = [ + ...$models, + [ + 'alias' => 'JMSPicture_mini', + 'type' => JMSPicture::class, + 'groups' => ['mini'], ], - 'body_listener' => false, - 'routing_loader' => false, - ]); + [ + 'alias' => 'BazingaUser_grouped', + 'type' => BazingaUser::class, + 'groups' => ['foo'], + ], + [ + 'alias' => 'JMSComplex', + 'type' => JMSComplex::class, + 'groups' => [ + 'list', + 'details', + 'User' => ['list'], + ], + ], + [ + 'alias' => 'JMSComplexDefault', + 'type' => JMSComplex::class, + 'groups' => null, + ], + ]; } // Filter routes @@ -267,46 +318,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ], ], 'models' => [ - 'names' => [ - [ - 'alias' => 'PrivateProtectedExposure', - 'type' => PrivateProtectedExposure::class, - ], - [ - 'alias' => 'JMSPicture_mini', - 'type' => JMSPicture::class, - 'groups' => ['mini'], - ], - [ - 'alias' => 'BazingaUser_grouped', - 'type' => BazingaUser::class, - 'groups' => ['foo'], - ], - [ - 'alias' => 'JMSComplex', - 'type' => JMSComplex::class, - 'groups' => [ - 'list', - 'details', - 'User' => ['list'], - ], - ], - [ - 'alias' => 'JMSComplexDefault', - 'type' => JMSComplex::class, - 'groups' => null, - ], - [ - 'alias' => 'SymfonyConstraintsTestGroup', - 'type' => SymfonyConstraintsWithValidationGroups::class, - 'groups' => ['test'], - ], - [ - 'alias' => 'SymfonyConstraintsDefaultGroup', - 'type' => SymfonyConstraintsWithValidationGroups::class, - 'groups' => null, - ], - ], + 'names' => $models, ], ]); @@ -315,17 +327,11 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $c->setDefinition('nelmio.test.jms.virtual_type.describer', $def); } - /** - * {@inheritdoc} - */ public function getCacheDir(): string { return parent::getCacheDir().'/'.$this->flags; } - /** - * {@inheritdoc} - */ public function getLogDir(): string { return parent::getLogDir().'/'.$this->flags; diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php index 846c59a42..51b9b7951 100644 --- a/Tests/Functional/WebTestCase.php +++ b/Tests/Functional/WebTestCase.php @@ -171,14 +171,19 @@ public function assertNotHasProperty($property, OA\AbstractAnnotation $annotatio } /** - * BC symfony < 5.3. + * BC symfony < 5.3 and symfony >= 7 + * + * KernelTestCase::getContainer has a Container return type object in symfony 7 + * which is incompatible with the return type of previous versions or + * at least the return type of overridden method (which was added for BC compatibility), + * hence moving it to the magic method. */ - protected static function getContainer(): ContainerInterface + public static function __callStatic(string $name, array $arguments) { - if (method_exists(parent::class, 'getContainer')) { - return parent::getContainer(); + if ('getContainer' === $name) { + return static::$container; } - return static::$container; + return null; } } diff --git a/Tests/Model/ModelRegistryTest.php b/Tests/Model/ModelRegistryTest.php index 2b36455cf..59f559d7b 100644 --- a/Tests/Model/ModelRegistryTest.php +++ b/Tests/Model/ModelRegistryTest.php @@ -151,8 +151,6 @@ public function testNameCollisionsAreLoggedWithAlternativeNames() /** * @dataProvider getNameAlternatives - * - * @param $expected */ public function testNameAliasingForObjects(string $expected, $groups, array $alternativeNames) { diff --git a/Tests/ModelDescriber/Annotations/AnnotationReaderTest.php b/Tests/ModelDescriber/Annotations/AnnotationReaderTest.php index b84d27944..4aedd3798 100644 --- a/Tests/ModelDescriber/Annotations/AnnotationReaderTest.php +++ b/Tests/ModelDescriber/Annotations/AnnotationReaderTest.php @@ -27,6 +27,7 @@ class AnnotationReaderTest extends TestCase /** * @param object $entity + * * @dataProvider provideProperty */ public function testProperty($entity) @@ -38,7 +39,11 @@ public function testProperty($entity) $schema->merge([new OA\Property(['property' => 'property2'] + $baseProps)]); $registry = new ModelRegistry([], new OA\OpenApi($baseProps), []); - $symfonyConstraintAnnotationReader = new OpenApiAnnotationsReader(new AnnotationReader(), $registry, ['json']); + $symfonyConstraintAnnotationReader = new OpenApiAnnotationsReader( + class_exists(AnnotationReader::class) ? new AnnotationReader() : null, + $registry, + ['json'] + ); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property2'), $schema->properties[1]); diff --git a/Tests/ModelDescriber/Annotations/Fixture/CompoundValidationRule.php b/Tests/ModelDescriber/Annotations/Fixture/CompoundValidationRule.php index bbfb42019..e6b5d3f74 100644 --- a/Tests/ModelDescriber/Annotations/Fixture/CompoundValidationRule.php +++ b/Tests/ModelDescriber/Annotations/Fixture/CompoundValidationRule.php @@ -14,6 +14,7 @@ class_alias(CompoundStub::class, Compound::class); /** * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] final class CompoundValidationRule extends Compound { protected function getConstraints(array $options): array diff --git a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php index 14a00741c..f1766bad2 100644 --- a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php +++ b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations; use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\ModelDescriber\Annotations\SymfonyConstraintAnnotationReader; use Nelmio\ApiDocBundle\Tests\Helper; use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert; @@ -19,30 +20,54 @@ use OpenApi\Context; use OpenApi\Generator; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints as Assert; class SymfonyConstraintAnnotationReaderTest extends TestCase { + /** + * @var AnnotationReader|null + */ + private $doctrineAnnotations; + + protected function setUp(): void + { + $this->doctrineAnnotations = class_exists(AnnotationReader::class) ? new AnnotationReader() : null; + } + public function testUpdatePropertyFix1283() { - $entity = new class() { - /** - * @Assert\NotBlank() - * @Assert\Length(min = 1) - */ - private $property1; - /** - * @Assert\NotBlank() - */ - private $property2; - }; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + $entity = new class() { + /** + * @Assert\NotBlank() + * + * @Assert\Length(min = 1) + */ + private $property1; + + /** + * @Assert\NotBlank() + */ + private $property2; + }; + } else { + $entity = new class() { + #[Assert\Length(min: 1)] + #[Assert\NotBlank()] + private $property1; + + #[Assert\NotBlank()] + private $property2; + }; + } $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property2'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -54,6 +79,7 @@ public function testUpdatePropertyFix1283() /** * @param object $entity + * * @dataProvider provideOptionalProperty */ public function testOptionalProperty($entity) @@ -66,7 +92,7 @@ public function testOptionalProperty($entity) $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property2'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -78,17 +104,23 @@ public function testOptionalProperty($entity) public function provideOptionalProperty(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\NotBlank(allowNull = true) - * @Assert\Length(min = 1) - */ - private $property1; - /** - * @Assert\NotBlank() - */ - private $property2; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\NotBlank(allowNull = true) + * + * @Assert\Length(min = 1) + */ + private $property1; + + /** + * @Assert\NotBlank() + */ + private $property2; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -103,6 +135,7 @@ public function provideOptionalProperty(): iterable /** * @param object $entity + * * @dataProvider provideAssertChoiceResultsInNumericArray */ public function testAssertChoiceResultsInNumericArray($entity) @@ -110,7 +143,7 @@ public function testAssertChoiceResultsInNumericArray($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -126,13 +159,18 @@ public function provideAssertChoiceResultsInNumericArray(): iterable 2 => 'blocked', ]); - yield 'Annotations' => [new class() { - /** - * @Assert\Length(min = 1) - * @Assert\Choice(choices=TEST_ASSERT_CHOICE_STATUSES) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Length(min = 1) + * + * @Assert\Choice(choices=TEST_ASSERT_CHOICE_STATUSES) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -145,6 +183,7 @@ public function provideAssertChoiceResultsInNumericArray(): iterable /** * @param object $entity + * * @dataProvider provideMultipleChoiceConstraintsApplyEnumToItems */ public function testMultipleChoiceConstraintsApplyEnumToItems($entity) @@ -152,7 +191,7 @@ public function testMultipleChoiceConstraintsApplyEnumToItems($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -163,12 +202,14 @@ public function testMultipleChoiceConstraintsApplyEnumToItems($entity) public function provideMultipleChoiceConstraintsApplyEnumToItems(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Choice(choices={"one", "two"}, multiple=true) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [new class() { + /** + * @Assert\Choice(choices={"one", "two"}, multiple=true) + */ + private $property1; + }]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -180,7 +221,9 @@ public function provideMultipleChoiceConstraintsApplyEnumToItems(): iterable /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1780 + * * @dataProvider provideLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet */ public function testLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet($entity) @@ -188,7 +231,7 @@ public function testLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -199,12 +242,16 @@ public function testLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet($entity) public function provideLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Length(min = 1) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Length(min = 1) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -216,7 +263,9 @@ public function provideLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet(): itera /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1780 + * * @dataProvider provideLengthConstraintDoesNotSetMinLengthIfMinIsNotSet */ public function testLengthConstraintDoesNotSetMinLengthIfMinIsNotSet($entity) @@ -224,7 +273,7 @@ public function testLengthConstraintDoesNotSetMinLengthIfMinIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -235,12 +284,16 @@ public function testLengthConstraintDoesNotSetMinLengthIfMinIsNotSet($entity) public function provideLengthConstraintDoesNotSetMinLengthIfMinIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Length(max = 100) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Length(max = 100) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -252,18 +305,25 @@ public function provideLengthConstraintDoesNotSetMinLengthIfMinIsNotSet(): itera public function testCompoundValidationRules() { - $entity = new class() { - /** - * @CustomAssert\CompoundValidationRule() - */ - private $property1; - }; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + $entity = new class() { + /** + * @CustomAssert\CompoundValidationRule() + */ + private $property1; + }; + } else { + $entity = new class() { + #[CustomAssert\CompoundValidationRule()] + private $property1; + }; + } $propertyName = 'property1'; $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => $propertyName])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, $propertyName), $schema->properties[0]); @@ -285,7 +345,9 @@ public function testCompoundValidationRules() /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1821 + * * @dataProvider provideCountConstraintDoesNotSetMinItemsIfMinIsNotSet */ public function testCountConstraintDoesNotSetMinItemsIfMinIsNotSet($entity) @@ -293,7 +355,7 @@ public function testCountConstraintDoesNotSetMinItemsIfMinIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -304,12 +366,16 @@ public function testCountConstraintDoesNotSetMinItemsIfMinIsNotSet($entity) public function provideCountConstraintDoesNotSetMinItemsIfMinIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Count(max = 10) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Count(max = 10) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -321,7 +387,9 @@ public function provideCountConstraintDoesNotSetMinItemsIfMinIsNotSet(): iterabl /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1821 + * * @dataProvider provideCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet */ public function testCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet($entity) @@ -329,7 +397,7 @@ public function testCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -340,12 +408,16 @@ public function testCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet($entity) public function provideCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Count(min = 10) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Count(min = 10) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -357,7 +429,9 @@ public function provideCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet(): iterabl /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1822 + * * @dataProvider provideRangeConstraintDoesNotSetMaximumIfMaxIsNotSet */ public function testRangeConstraintDoesNotSetMaximumIfMaxIsNotSet($entity) @@ -365,7 +439,7 @@ public function testRangeConstraintDoesNotSetMaximumIfMaxIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -376,12 +450,16 @@ public function testRangeConstraintDoesNotSetMaximumIfMaxIsNotSet($entity) public function provideRangeConstraintDoesNotSetMaximumIfMaxIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Range(min = 10) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Range(min = 10) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -393,7 +471,9 @@ public function provideRangeConstraintDoesNotSetMaximumIfMaxIsNotSet(): iterable /** * @param object $entity + * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1822 + * * @dataProvider provideRangeConstraintDoesNotSetMinimumIfMinIsNotSet */ public function testRangeConstraintDoesNotSetMinimumIfMinIsNotSet($entity) @@ -401,7 +481,7 @@ public function testRangeConstraintDoesNotSetMinimumIfMinIsNotSet($entity) $schema = $this->createObj(OA\Schema::class, []); $schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]); - $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader()); + $symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($this->doctrineAnnotations); $symfonyConstraintAnnotationReader->setSchema($schema); $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); @@ -412,12 +492,16 @@ public function testRangeConstraintDoesNotSetMinimumIfMinIsNotSet($entity) public function provideRangeConstraintDoesNotSetMinimumIfMinIsNotSet(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\Range(max = 10) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [ + new class() { + /** + * @Assert\Range(max = 10) + */ + private $property1; + } + ]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -432,6 +516,7 @@ public function provideRangeConstraintDoesNotSetMinimumIfMinIsNotSet(): iterable * group when `group={"someGroup"}` is not set. * * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1857 + * * @dataProvider provideRangeConstraintDoesNotSetMinimumIfMinIsNotSet */ public function testReaderWithValidationGroupsEnabledChecksForDefaultGroupWhenNoSerializationGroupsArePassed($entity) @@ -452,6 +537,7 @@ public function testReaderWithValidationGroupsEnabledChecksForDefaultGroupWhenNo /** * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1857 + * * @dataProvider provideConstraintsWithGroups */ public function testReaderWithValidationGroupsEnabledDoesNotReadAnnotationsWithoutDefaultGroupIfNoGroupsArePassed($entity) @@ -475,6 +561,7 @@ public function testReaderWithValidationGroupsEnabledDoesNotReadAnnotationsWitho /** * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1857 + * * @dataProvider provideConstraintsWithGroups */ public function testReaderWithValidationGroupsEnabledReadsOnlyConstraintsWithGroupsProvided($entity) @@ -499,6 +586,7 @@ public function testReaderWithValidationGroupsEnabledReadsOnlyConstraintsWithGro /** * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1857 + * * @dataProvider provideConstraintsWithGroups */ public function testReaderWithValidationGroupsEnabledCanReadFromMultipleValidationGroups($entity) @@ -523,13 +611,16 @@ public function testReaderWithValidationGroupsEnabledCanReadFromMultipleValidati public function provideConstraintsWithGroups(): iterable { - yield 'Annotations' => [new class() { - /** - * @Assert\NotBlank() - * @Assert\Range(min=1, groups={"other"}) - */ - private $property1; - }]; + if (interface_exists(Reader::class) && Kernel::MAJOR_VERSION < 7) { + yield 'Annotations' => [new class() { + /** + * @Assert\NotBlank() + * + * @Assert\Range(min=1, groups={"other"}) + */ + private $property1; + }]; + } if (\PHP_VERSION_ID >= 80000) { yield 'Attributes' => [new class() { @@ -543,7 +634,7 @@ public function provideConstraintsWithGroups(): iterable private function createConstraintReaderWithValidationGroupsEnabled(): SymfonyConstraintAnnotationReader { return new SymfonyConstraintAnnotationReader( - new AnnotationReader(), + $this->doctrineAnnotations, true ); } diff --git a/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php b/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php index 49271dffb..20fb7582b 100644 --- a/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php +++ b/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php @@ -24,8 +24,8 @@ class ApplyOpenApiDiscriminatorTraitTest extends TestCase { use ApplyOpenApiDiscriminatorTrait; - const GROUPS = ['test']; - const OPTIONS = ['test' => 123]; + public const GROUPS = ['test']; + public const OPTIONS = ['test' => 123]; private $schema; diff --git a/Tests/Render/RenderOpenApiTest.php b/Tests/Render/RenderOpenApiTest.php index 514b0c822..c8d6cc164 100644 --- a/Tests/Render/RenderOpenApiTest.php +++ b/Tests/Render/RenderOpenApiTest.php @@ -11,7 +11,6 @@ namespace Nelmio\ApiDocBundle\Tests\Render; -use InvalidArgumentException; use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; @@ -35,14 +34,14 @@ public function testRender() public function testUnknownFormat() { $availableOpenApiRenderers = []; - $this->expectExceptionObject(new InvalidArgumentException(sprintf('Format "%s" is not supported.', $this->format))); + $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Format "%s" is not supported.', $this->format))); $this->renderOpenApi(...$availableOpenApiRenderers); } public function testUnknownArea() { $this->hasArea = false; - $this->expectExceptionObject(new InvalidArgumentException(sprintf('Area "%s" is not supported.', $this->area))); + $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Area "%s" is not supported.', $this->area))); $this->renderOpenApi(); } diff --git a/Tests/RouteDescriber/FosRestDescriberTest.php b/Tests/RouteDescriber/FosRestDescriberTest.php index 6219d718b..5d94b7b42 100644 --- a/Tests/RouteDescriber/FosRestDescriberTest.php +++ b/Tests/RouteDescriber/FosRestDescriberTest.php @@ -16,6 +16,7 @@ use Nelmio\ApiDocBundle\RouteDescriber\FosRestDescriber; use OpenApi\Annotations\OpenApi; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Route; use Symfony\Component\Validator\Constraints\Choice; @@ -23,15 +24,22 @@ class FosRestDescriberTest extends TestCase { public function testQueryParamWithChoiceConstraintIsAddedAsEnum() { + if (Kernel::MAJOR_VERSION >= 7) { + $this->markTestSkipped('FosRest is not supported in symfony 7'); + } + $choices = ['foo', 'bar']; $queryParam = new QueryParam(); $queryParam->requirements = new Choice($choices); - $readerMock = $this->createMock(Reader::class); - $readerMock->method('getMethodAnnotations')->willReturn([ - $queryParam, - ]); + $readerMock = null; + if (interface_exists(Reader::class)) { + $readerMock = $this->createMock(Reader::class); + $readerMock->method('getMethodAnnotations')->willReturn([ + $queryParam, + ]); + } $fosRestDescriber = new FosRestDescriber($readerMock, []); $api = new OpenApi([]); diff --git a/Tests/Routing/FilteredRouteCollectionBuilderTest.php b/Tests/Routing/FilteredRouteCollectionBuilderTest.php index f9eec1d6a..d4f63b593 100644 --- a/Tests/Routing/FilteredRouteCollectionBuilderTest.php +++ b/Tests/Routing/FilteredRouteCollectionBuilderTest.php @@ -31,6 +31,16 @@ */ class FilteredRouteCollectionBuilderTest extends TestCase { + /** + * @var AnnotationReader|null + */ + private $doctrineAnnotations; + + protected function setUp(): void + { + $this->doctrineAnnotations = class_exists(AnnotationReader::class) ? new AnnotationReader() : null; + } + public function testFilter() { $options = [ @@ -50,7 +60,7 @@ public function testFilter() } $routeBuilder = new FilteredRouteCollectionBuilder( - new AnnotationReader(), + $this->doctrineAnnotations, $this->createControllerReflector(), 'areaName', $options @@ -62,6 +72,7 @@ public function testFilter() /** * @group legacy + * * @expectedDeprecation Passing an indexed array with a collection of path patterns as argument 1 for `Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder::__construct()` is deprecated since 3.2.0, expected structure is an array containing parameterized options. */ public function testFilterWithDeprecatedArgument() @@ -77,7 +88,7 @@ public function testFilterWithDeprecatedArgument() } $routeBuilder = new FilteredRouteCollectionBuilder( - new AnnotationReader(), + $this->doctrineAnnotations, $this->createControllerReflector(), 'areaName', $pathPattern @@ -95,7 +106,7 @@ public function testFilterWithInvalidOption(array $options) $this->expectException(InvalidArgumentException::class); new FilteredRouteCollectionBuilder( - new AnnotationReader(), + $this->doctrineAnnotations, $this->createControllerReflector(), 'areaName', $options @@ -148,7 +159,7 @@ public function testMatchingRoutes(string $name, Route $route, array $options = $routes->add($name, $route); $routeBuilder = new FilteredRouteCollectionBuilder( - new AnnotationReader(), + $this->doctrineAnnotations, $this->createControllerReflector(), 'area', $options @@ -176,6 +187,7 @@ public function getMatchingRoutes(): iterable /** * @group test + * * @dataProvider getMatchingRoutesWithAnnotation */ public function testMatchingRoutesWithAnnotation(string $name, Route $route, array $options = []) @@ -188,12 +200,15 @@ public function testMatchingRoutesWithAnnotation(string $name, Route $route, arr $controllerReflectorStub = $this->createMock(ControllerReflector::class); $controllerReflectorStub->method('getReflectionMethod')->willReturn($reflectionMethodStub); - $annotationReader = $this->createMock(Reader::class); - $annotationReader - ->method('getMethodAnnotation') - ->with($reflectionMethodStub, Areas::class) - ->willReturn(new Areas(['value' => [$area]])) - ; + $annotationReader = null; + if (interface_exists(Reader::class)) { + $annotationReader = $this->createMock(Reader::class); + $annotationReader + ->method('getMethodAnnotation') + ->with($reflectionMethodStub, Areas::class) + ->willReturn(new Areas(['value' => [$area]])) + ; + } $routeBuilder = new FilteredRouteCollectionBuilder( $annotationReader, @@ -246,7 +261,7 @@ public function testNonMatchingRoutes(string $name, Route $route, array $options $routes->add($name, $route); $routeBuilder = new FilteredRouteCollectionBuilder( - new AnnotationReader(), + $this->doctrineAnnotations, $this->createControllerReflector(), 'areaName', $options @@ -289,11 +304,14 @@ public function testRoutesWithDisabledDefaultRoutes( $controllerReflectorStub = $this->createMock(ControllerReflector::class); $controllerReflectorStub->method('getReflectionMethod')->willReturn($reflectionMethodStub); - $annotationReader = $this->createMock(Reader::class); - $annotationReader - ->method('getMethodAnnotations') - ->willReturn($annotations) - ; + $annotationReader = null; + if (interface_exists(Reader::class)) { + $annotationReader = $this->createMock(Reader::class); + $annotationReader + ->method('getMethodAnnotations') + ->willReturn($annotations) + ; + } $routeBuilder = new FilteredRouteCollectionBuilder( $annotationReader, diff --git a/Tests/Util/ControllerReflectorTest.php b/Tests/Util/ControllerReflectorTest.php index d44b0dbde..50a6408ff 100644 --- a/Tests/Util/ControllerReflectorTest.php +++ b/Tests/Util/ControllerReflectorTest.php @@ -5,7 +5,6 @@ use Nelmio\ApiDocBundle\Tests\Functional\Controller\BazingaController; use Nelmio\ApiDocBundle\Util\ControllerReflector; use PHPUnit\Framework\TestCase; -use ReflectionMethod; use Symfony\Component\DependencyInjection\Container; class ControllerReflectorTest extends TestCase @@ -14,11 +13,11 @@ public function testGetReflectionMethod(): void { $controllerReflector = new ControllerReflector(new Container()); $this->assertEquals( - ReflectionMethod::class, + \ReflectionMethod::class, get_class($controllerReflector->getReflectionMethod([BazingaController::class, 'userAction'])) ); $this->assertEquals( - ReflectionMethod::class, + \ReflectionMethod::class, get_class($controllerReflector->getReflectionMethod(BazingaController::class.'::userAction')) ); $this->assertNull( diff --git a/composer.json b/composer.json index e332fa6bb..3c38154ec 100644 --- a/composer.json +++ b/composer.json @@ -17,40 +17,39 @@ "require": { "php": ">=7.2", "ext-json": "*", - "doctrine/annotations": "^2.0", "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.0|^2.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4.24|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/options-resolver": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4.24|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/options-resolver": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "zircote/swagger-php": "^4.2.15", "phpdocumentor/reflection-docblock": "^3.1|^4.0|^5.0" }, "require-dev": { "sensio/framework-extra-bundle": "^5.4|^6.0", - "symfony/asset": "^5.4|^6.0", - "symfony/dom-crawler": "^5.4|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/cache": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", "symfony/phpunit-bridge": "^6.3.2", - "symfony/property-access": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/templating": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/templating": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", "api-platform/core": "^2.7.0|^3", "symfony/deprecation-contracts": "^2.1|^3", - "friendsofsymfony/rest-bundle": "^2.8|^3.0", "willdurand/hateoas-bundle": "^1.0|^2.0", "jms/serializer-bundle": "^2.3|^3.0|^4.0|^5.0@beta", @@ -59,6 +58,7 @@ }, "suggest": { "api-platform/core": "For using an API oriented framework.", + "doctrine/annotations": "For using doctrine annotations", "friendsofsymfony/rest-bundle": "For using the parameters annotations.", "jms/serializer-bundle": "For describing your models.", "symfony/asset": "For using the Swagger UI.",