diff --git a/src/Api/FormatsProvider.php b/src/Api/FormatsProvider.php index 9efdde324e3..b73bfdc9781 100644 --- a/src/Api/FormatsProvider.php +++ b/src/Api/FormatsProvider.php @@ -20,6 +20,8 @@ * {@inheritdoc} * * @author Anthony GRASSIOT + * + * @deprecated since API Platform 2.5, use the "formats" attribute instead */ final class FormatsProvider implements FormatsProviderInterface, OperationAwareFormatsProviderInterface { @@ -28,6 +30,8 @@ final class FormatsProvider implements FormatsProviderInterface, OperationAwareF public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $configuredFormats) { + @trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "formats" attribute instead.', __CLASS__), E_USER_DEPRECATED); + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->configuredFormats = $configuredFormats; } diff --git a/src/Api/FormatsProviderInterface.php b/src/Api/FormatsProviderInterface.php index b6f2e02ad7b..ea979141a28 100644 --- a/src/Api/FormatsProviderInterface.php +++ b/src/Api/FormatsProviderInterface.php @@ -17,6 +17,8 @@ * Extracts formats for a given operation according to the retrieved Metadata. * * @author Anthony GRASSIOT + * + * @deprecated since API Platform 2.5, use the "formats" attribute instead */ interface FormatsProviderInterface { diff --git a/src/Api/OperationAwareFormatsProviderInterface.php b/src/Api/OperationAwareFormatsProviderInterface.php index 53c74e27015..7860aa1ff0f 100644 --- a/src/Api/OperationAwareFormatsProviderInterface.php +++ b/src/Api/OperationAwareFormatsProviderInterface.php @@ -17,6 +17,8 @@ * Extracts formats for a given operation according to the retrieved Metadata. * * @author Anthony GRASSIOT + * + * @deprecated since API Platform 2.5, use the "formats" attribute instead */ interface OperationAwareFormatsProviderInterface extends FormatsProviderInterface { diff --git a/src/Api/OperationMethodResolverInterface.php b/src/Api/OperationMethodResolverInterface.php index 56010667092..e52f2064a26 100644 --- a/src/Api/OperationMethodResolverInterface.php +++ b/src/Api/OperationMethodResolverInterface.php @@ -19,6 +19,8 @@ * Resolves the uppercased HTTP method associated with an operation. * * @author Kévin Dunglas + * + * @deprecated since API Platform 2.5, use the "method" attribute instead */ interface OperationMethodResolverInterface { diff --git a/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php b/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php index 27fd1b43eec..4198cf02e0e 100644 --- a/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php +++ b/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php @@ -15,7 +15,6 @@ use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\Documentation\Documentation; -use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; @@ -42,7 +41,7 @@ final class SwaggerUiAction private $description; private $version; private $showWebby; - private $formats = []; + private $formats; private $oauthEnabled; private $oauthClientId; private $oauthClientSecret; @@ -56,10 +55,7 @@ final class SwaggerUiAction private $reDocEnabled; private $graphqlEnabled; - /** - * @throws InvalidArgumentException - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', /* FormatsProviderInterface */ $formatsProvider = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', $formats = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -82,32 +78,33 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName $this->reDocEnabled = $reDocEnabled; $this->graphqlEnabled = $graphqlEnabled; - if (\is_array($formatsProvider)) { - if ($formatsProvider) { - // Only trigger notification for non-default argument - @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); - } - $this->formats = $formatsProvider; + if (\is_array($formats)) { + $this->formats = $formats; return; } - if (!$formatsProvider instanceof FormatsProviderInterface) { - throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); - } - $this->formatsProvider = $formatsProvider; + @trigger_error(sprintf('Passing an array or an instance of "%s" as 5th parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an array instead', FormatsProviderInterface::class, __CLASS__), E_USER_DEPRECATED); + $this->formatsProvider = $formats; } public function __invoke(Request $request) { + $attributes = RequestAttributesExtractor::extractAttributes($request); + // BC check to be removed in 3.0 - if (null !== $this->formatsProvider) { - $this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request)); + if (null === $this->formatsProvider) { + $formats = $attributes ? $this + ->resourceMetadataFactory + ->create($attributes['resource_class']) + ->getOperationAttribute($attributes, 'output_formats', [], true) : $this->formats; + } else { + $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); } - $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats); + $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version); - return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation))); + return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation) + ['formats' => $formats])); } /** @@ -118,7 +115,6 @@ private function getContext(Request $request, Documentation $documentation): arr $context = [ 'title' => $this->title, 'description' => $this->description, - 'formats' => $this->formats, 'showWebby' => $this->showWebby, 'swaggerUiEnabled' => $this->swaggerUiEnabled, 'reDocEnabled' => $this->reDocEnabled, diff --git a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php index 64555053c72..9bc2fe2143d 100644 --- a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php @@ -40,7 +40,7 @@ final class SwaggerCommand extends Command private $apiVersion; private $apiFormats; - public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats) + public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats = null) { $this->normalizer = $normalizer; $this->resourceNameCollectionFactory = $resourceNameCollection; @@ -49,6 +49,10 @@ public function __construct(NormalizerInterface $normalizer, ResourceNameCollect $this->apiVersion = $apiVersion; $this->apiFormats = $apiFormats; + if (null !== $apiFormats) { + @trigger_error(sprintf('Passing a 6th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), E_USER_DEPRECATED); + } + parent::__construct(); } diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 7aeda770021..768cc4ab6fb 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -87,9 +87,15 @@ public function load(array $configs, ContainerBuilder $container): void $config = $this->processConfiguration($configuration, $configs); $formats = $this->getFormats($config['formats']); + $patchFormats = $this->getFormats($config['patch_formats']); $errorFormats = $this->getFormats($config['error_formats']); - $this->registerCommonConfiguration($container, $config, $loader, $formats, $errorFormats); + // Backward Compatibility layer + if (isset($formats['jsonapi']) && !isset($patchFormats['jsonapi'])) { + $patchFormats['jsonapi'] = ['application/vnd.api+json']; + } + + $this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats); $this->registerMetadataConfiguration($container, $config, $loader); $this->registerOAuthConfiguration($container, $config); $this->registerSwaggerConfiguration($container, $config, $loader); @@ -127,7 +133,7 @@ public function load(array $configs, ContainerBuilder $container): void } } - private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $errorFormats): void + private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats): void { $loader->load('api.xml'); $loader->load('data_persister.xml'); @@ -146,6 +152,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array $container->setParameter('api_platform.show_webby', $config['show_webby']); $container->setParameter('api_platform.exception_to_status', $config['exception_to_status']); $container->setParameter('api_platform.formats', $formats); + $container->setParameter('api_platform.patch_formats', $patchFormats); $container->setParameter('api_platform.error_formats', $errorFormats); $container->setParameter('api_platform.allow_plain_identifiers', $config['allow_plain_identifiers']); $container->setParameter('api_platform.eager_loading.enabled', $this->isConfigEnabled($container, $config['eager_loading'])); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index b1c94141152..1bbc24c4fbc 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -163,6 +163,7 @@ public function getConfigTreeBuilder() 'json' => ['mime_types' => ['application/json']], // Swagger support 'html' => ['mime_types' => ['text/html']], // Swagger UI support ]); + $this->addFormatSection($rootNode, 'patch_formats', []); $this->addFormatSection($rootNode, 'error_formats', [ 'jsonproblem' => ['mime_types' => ['application/problem+json']], 'jsonld' => ['mime_types' => ['application/ld+json']], diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index f618875473d..3b0cd1f180b 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -18,6 +18,7 @@ + The "%service_id%" service is deprecated since API Platform 2.5. @@ -68,13 +69,18 @@ %api_platform.formats% + The "%service_id%" service is deprecated since API Platform 2.5. + + + + The "%alias_id%" alias is deprecated since API Platform 2.5. - + %api_platform.patch_formats% @@ -150,7 +156,8 @@ - + + %api_platform.formats% @@ -177,7 +184,6 @@ - @@ -231,7 +237,6 @@ %api_platform.title% %api_platform.description% %api_platform.version% - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml index 8b6c4bf7a4c..2490d5cf500 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml @@ -10,7 +10,7 @@ - + null diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml index e07c1e1ee7b..1a50a9b0e14 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml @@ -24,7 +24,13 @@ + %api_platform.patch_formats% + + + + %api_platform.formats% + %api_platform.patch_formats% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml index 753184f1fa2..62542a40e5d 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml @@ -19,7 +19,7 @@ %api_platform.title% %api_platform.description% %api_platform.version% - + %api_platform.formats% %api_platform.oauth.enabled% %api_platform.oauth.clientId% %api_platform.oauth.clientSecret% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml index af3ac59bd50..9b2539356bc 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -11,7 +11,7 @@ - + null null @@ -28,7 +28,7 @@ %api_platform.collection.pagination.page_parameter_name% %api_platform.collection.pagination.client_items_per_page% %api_platform.collection.pagination.items_per_page_parameter_name% - + %api_platform.formats% %api_platform.collection.pagination.client_enabled% %api_platform.collection.pagination.enabled_parameter_name% @@ -45,7 +45,6 @@ %api_platform.title% %api_platform.description% %api_platform.version% - %api_platform.formats% diff --git a/src/Bridge/Symfony/Routing/ApiLoader.php b/src/Bridge/Symfony/Routing/ApiLoader.php index 9f7ed5caa5a..c6bdd4d2971 100644 --- a/src/Bridge/Symfony/Routing/ApiLoader.php +++ b/src/Bridge/Symfony/Routing/ApiLoader.php @@ -186,6 +186,10 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas $resourceShortName = $resourceMetadata->getShortName(); if (isset($operation['route_name'])) { + if (!isset($operation['method'])) { + @trigger_error(sprintf('Not setting the "method" attribute is deprecated and will not be supported anymore in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', OperationType::COLLECTION === $operationType ? 'collection' : 'item', $operationName, $resourceClass), E_USER_DEPRECATED); + } + return; } diff --git a/src/Bridge/Symfony/Routing/OperationMethodResolver.php b/src/Bridge/Symfony/Routing/OperationMethodResolver.php index e7dcf88f2df..c808f187730 100644 --- a/src/Bridge/Symfony/Routing/OperationMethodResolver.php +++ b/src/Bridge/Symfony/Routing/OperationMethodResolver.php @@ -25,6 +25,8 @@ * * @author Kévin Dunglas * @author Teoh Han Hui + * + * @deprecated since API Platform 2.5, use the "method" attribute instead */ final class OperationMethodResolver implements OperationMethodResolverInterface { @@ -33,6 +35,8 @@ final class OperationMethodResolver implements OperationMethodResolverInterface public function __construct(RouterInterface $router, ResourceMetadataFactoryInterface $resourceMetadataFactory) { + @trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "method" attribute instead.', __CLASS__), E_USER_DEPRECATED); + $this->router = $router; $this->resourceMetadataFactory = $resourceMetadataFactory; } diff --git a/src/Bridge/Symfony/Routing/OperationMethodResolverInterface.php b/src/Bridge/Symfony/Routing/OperationMethodResolverInterface.php index 6fdd8550c59..8b1ef46a3d3 100644 --- a/src/Bridge/Symfony/Routing/OperationMethodResolverInterface.php +++ b/src/Bridge/Symfony/Routing/OperationMethodResolverInterface.php @@ -21,6 +21,8 @@ * Resolves the HTTP method associated with an operation, extended for Symfony routing. * * @author Teoh Han Hui + * + * @deprecated since API Platform 2.5, use the "method" attribute instead */ interface OperationMethodResolverInterface extends BaseOperationMethodResolverInterface { diff --git a/src/Documentation/Action/DocumentationAction.php b/src/Documentation/Action/DocumentationAction.php index 14fbc1b2ae8..9b6a91180d1 100644 --- a/src/Documentation/Action/DocumentationAction.php +++ b/src/Documentation/Action/DocumentationAction.php @@ -15,7 +15,6 @@ use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\Documentation\Documentation; -use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; use Symfony\Component\HttpFoundation\Request; @@ -31,31 +30,27 @@ final class DocumentationAction private $title; private $description; private $version; - private $formats = []; + private $formats; private $formatsProvider; + private $resourceMetadataFactory; - /** - * @throws InvalidArgumentException - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', /* FormatsProviderInterface */ $formatsProvider = []) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->title = $title; $this->description = $description; $this->version = $version; + + if (null === $formatsProvider) { + return; + } + + @trigger_error(sprintf('Passing an array or an instance of "%s" as 5th parameter of the constructor of "%s" is deprecated since API Platform 2.5', FormatsProviderInterface::class, __CLASS__), E_USER_DEPRECATED); if (\is_array($formatsProvider)) { - if ($formatsProvider) { - // Only trigger notification for non-default argument - @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); - } $this->formats = $formatsProvider; return; } - if (!$formatsProvider instanceof FormatsProviderInterface) { - throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); - } - $this->formatsProvider = $formatsProvider; } diff --git a/src/Documentation/Documentation.php b/src/Documentation/Documentation.php index f01dc557828..53f5372df28 100644 --- a/src/Documentation/Documentation.php +++ b/src/Documentation/Documentation.php @@ -28,12 +28,19 @@ final class Documentation private $version; private $mimeTypes = []; - public function __construct(ResourceNameCollection $resourceNameCollection, string $title = '', string $description = '', string $version = '', array $formats = []) + public function __construct(ResourceNameCollection $resourceNameCollection, string $title = '', string $description = '', string $version = '', array $formats = null) { $this->resourceNameCollection = $resourceNameCollection; $this->title = $title; $this->description = $description; $this->version = $version; + + if (null === $formats) { + return; + } + + @trigger_error(sprintf('Passing a 5th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), E_USER_DEPRECATED); + foreach ($formats as $mimeTypes) { foreach ($mimeTypes as $mimeType) { $this->mimeTypes[] = $mimeType; @@ -43,6 +50,8 @@ public function __construct(ResourceNameCollection $resourceNameCollection, stri public function getMimeTypes(): array { + @trigger_error(sprintf('The method "%s" is deprecated since API Platform 2.5, use the "formats" attribute instead', __METHOD__), E_USER_DEPRECATED); + return $this->mimeTypes; } diff --git a/src/EventListener/AddFormatListener.php b/src/EventListener/AddFormatListener.php index 781a1287c8d..11f19d630ee 100644 --- a/src/EventListener/AddFormatListener.php +++ b/src/EventListener/AddFormatListener.php @@ -15,7 +15,7 @@ use ApiPlatform\Core\Api\FormatMatcher; use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; use Negotiation\Negotiator; use Symfony\Component\HttpFoundation\Request; @@ -31,26 +31,28 @@ final class AddFormatListener { private $negotiator; + private $resourceMetadataFactory; private $formats = []; - private $mimeTypes; private $formatsProvider; private $formatMatcher; /** - * @throws InvalidArgumentException + * @param ResourceMetadataFactoryInterface|FormatsProviderInterface|array $resourceMetadataFactory */ - public function __construct(Negotiator $negotiator, /* FormatsProviderInterface */ $formatsProvider) + public function __construct(Negotiator $negotiator, $resourceMetadataFactory, array $formats = []) { $this->negotiator = $negotiator; - if (\is_array($formatsProvider)) { - @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); - $this->formats = $formatsProvider; - } else { - if (!$formatsProvider instanceof FormatsProviderInterface) { - throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); - } + $this->resourceMetadataFactory = $resourceMetadataFactory instanceof ResourceMetadataFactoryInterface ? $resourceMetadataFactory : null; + $this->formats = $formats; + + if (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { + @trigger_error(sprintf('Passing an array or an instance of "%s" as 2nd parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an instance of "%s" instead', FormatsProviderInterface::class, __CLASS__, ResourceMetadataFactoryInterface::class), E_USER_DEPRECATED); + } - $this->formatsProvider = $formatsProvider; + if (\is_array($resourceMetadataFactory)) { + $this->formats = $resourceMetadataFactory; + } elseif ($resourceMetadataFactory instanceof FormatsProviderInterface) { + $this->formatsProvider = $resourceMetadataFactory; } } @@ -63,25 +65,46 @@ public function __construct(Negotiator $negotiator, /* FormatsProviderInterface public function onKernelRequest(GetResponseEvent $event): void { $request = $event->getRequest(); - if (!($request->attributes->has('_api_resource_class') || $request->attributes->getBoolean('_api_respond', false) || $request->attributes->getBoolean('_graphql', false))) { + if ( + !($request->attributes->has('_api_resource_class') + || $request->attributes->getBoolean('_api_respond', false) + || $request->attributes->getBoolean('_graphql', false)) + ) { return; } + + $attributes = RequestAttributesExtractor::extractAttributes($request); + // BC check to be removed in 3.0 - if (null !== $this->formatsProvider) { - $this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request)); + if ($this->resourceMetadataFactory) { + if ($attributes) { + // TODO: Subresource operation metadata aren't available by default, for now we have to fallback on default formats. + // TODO: A better approach would be to always populate the subresource operation array. + $formats = $this + ->resourceMetadataFactory + ->create($attributes['resource_class']) + ->getOperationAttribute($attributes, 'output_formats', $this->formats, true); + } else { + $formats = $this->formats; + } + } elseif ($this->formatsProvider instanceof FormatsProviderInterface) { + $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); + } else { + $formats = $this->formats; } - $this->formatMatcher = new FormatMatcher($this->formats); - $this->populateMimeTypes(); - $this->addRequestFormats($request, $this->formats); + $this->addRequestFormats($request, $formats); + $this->formatMatcher = new FormatMatcher($formats); // Empty strings must be converted to null because the Symfony router doesn't support parameter typing before 3.2 (_format) if (null === $routeFormat = $request->attributes->get('_format') ?: null) { - $mimeTypes = array_keys($this->mimeTypes); - } elseif (!isset($this->formats[$routeFormat])) { + $flattenedMimeTypes = $this->flattenMimeTypes($formats); + $mimeTypes = array_keys($flattenedMimeTypes); + } elseif (!isset($formats[$routeFormat])) { throw new NotFoundHttpException(sprintf('Format "%s" is not supported', $routeFormat)); } else { $mimeTypes = Request::getMimeTypes($routeFormat); + $flattenedMimeTypes = $this->flattenMimeTypes([$routeFormat => $mimeTypes]); } // First, try to guess the format from the Accept header @@ -89,7 +112,7 @@ public function onKernelRequest(GetResponseEvent $event): void $accept = $request->headers->get('Accept'); if (null !== $accept) { if (null === $mediaType = $this->negotiator->getBest($accept, $mimeTypes)) { - throw $this->getNotAcceptableHttpException($accept, $mimeTypes); + throw $this->getNotAcceptableHttpException($accept, $flattenedMimeTypes); } $request->setRequestFormat($this->formatMatcher->getFormat($mediaType->getType())); @@ -102,15 +125,15 @@ public function onKernelRequest(GetResponseEvent $event): void if (null !== $requestFormat) { $mimeType = $request->getMimeType($requestFormat); - if (isset($this->mimeTypes[$mimeType])) { + if (isset($flattenedMimeTypes[$mimeType])) { return; } - throw $this->getNotAcceptableHttpException($mimeType); + throw $this->getNotAcceptableHttpException($mimeType, $flattenedMimeTypes); } // Finally, if no Accept header nor Symfony request format is set, return the default format - foreach ($this->formats as $format => $mimeType) { + foreach ($formats as $format => $mimeType) { $request->setRequestFormat($format); return; @@ -130,37 +153,29 @@ private function addRequestFormats(Request $request, array $formats): void } /** - * Populates the $mimeTypes property. + * Retries the flattened list of MIME types. */ - private function populateMimeTypes(): void + private function flattenMimeTypes(array $formats): array { - if (null !== $this->mimeTypes) { - return; - } - - $this->mimeTypes = []; - foreach ($this->formats as $format => $mimeTypes) { + $flattenedMimeTypes = []; + foreach ($formats as $format => $mimeTypes) { foreach ($mimeTypes as $mimeType) { - $this->mimeTypes[$mimeType] = $format; + $flattenedMimeTypes[$mimeType] = $format; } } + + return $flattenedMimeTypes; } /** * Retrieves an instance of NotAcceptableHttpException. - * - * @param string[]|null $mimeTypes */ - private function getNotAcceptableHttpException(string $accept, array $mimeTypes = null): NotAcceptableHttpException + private function getNotAcceptableHttpException(string $accept, array $mimeTypes): NotAcceptableHttpException { - if (null === $mimeTypes) { - $mimeTypes = array_keys($this->mimeTypes); - } - return new NotAcceptableHttpException(sprintf( 'Requested format "%s" is not supported. Supported MIME types are "%s".', $accept, - implode('", "', $mimeTypes) + implode('", "', array_keys($mimeTypes)) )); } } diff --git a/src/EventListener/DeserializeListener.php b/src/EventListener/DeserializeListener.php index bfeef123614..ae0eb992d5a 100644 --- a/src/EventListener/DeserializeListener.php +++ b/src/EventListener/DeserializeListener.php @@ -15,7 +15,6 @@ use ApiPlatform\Core\Api\FormatMatcher; use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; @@ -39,28 +38,27 @@ final class DeserializeListener private $serializer; private $serializerContextBuilder; - private $formats = []; + private $formats; private $formatsProvider; - private $formatMatcher; /** - * @throws InvalidArgumentException + * @param ResourceMetadataFactoryInterface|FormatsProviderInterface|array $resourceMetadataFactory */ - public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, /* FormatsProviderInterface */$formatsProvider, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, $resourceMetadataFactory, ResourceMetadataFactoryInterface $legacyResourceMetadataFactory = null) { $this->serializer = $serializer; $this->serializerContextBuilder = $serializerContextBuilder; - if (\is_array($formatsProvider)) { - @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); - $this->formats = $formatsProvider; - } else { - if (!$formatsProvider instanceof FormatsProviderInterface) { - throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); - } + $this->resourceMetadataFactory = $resourceMetadataFactory instanceof ResourceMetadataFactoryInterface ? $resourceMetadataFactory : $legacyResourceMetadataFactory; - $this->formatsProvider = $formatsProvider; + if (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { + @trigger_error(sprintf('Passing an array or an instance of "%s" as 3rd parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an instance of "%s" instead', FormatsProviderInterface::class, __CLASS__, ResourceMetadataFactoryInterface::class), E_USER_DEPRECATED); + } + + if (\is_array($resourceMetadataFactory)) { + $this->formats = $resourceMetadataFactory; + } elseif ($resourceMetadataFactory instanceof FormatsProviderInterface) { + $this->formatsProvider = $resourceMetadataFactory; } - $this->resourceMetadataFactory = $resourceMetadataFactory; } /** @@ -86,12 +84,18 @@ public function onKernelRequest(GetResponseEvent $event): void $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes); // BC check to be removed in 3.0 - if (null !== $this->formatsProvider) { - $this->formats = $this->formatsProvider->getFormatsFromAttributes($attributes); + if ($this->resourceMetadataFactory) { + $formats = $this + ->resourceMetadataFactory + ->create($attributes['resource_class']) + ->getOperationAttribute($attributes, 'input_formats', [], true); + } elseif ($this->formatsProvider instanceof FormatsProviderInterface) { + $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); + } else { + $formats = $this->formats; } - $this->formatMatcher = new FormatMatcher($this->formats); - $format = $this->getFormat($request); + $format = $this->getFormat($request, $formats); $data = $request->attributes->get('data'); if (null !== $data) { $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data; @@ -108,7 +112,7 @@ public function onKernelRequest(GetResponseEvent $event): void * * @throws UnsupportedMediaTypeHttpException */ - private function getFormat(Request $request): string + private function getFormat(Request $request, array $formats): string { /** * @var string|null @@ -118,10 +122,11 @@ private function getFormat(Request $request): string throw new UnsupportedMediaTypeHttpException('The "Content-Type" header must exist.'); } - $format = $this->formatMatcher->getFormat($contentType); - if (null === $format || !isset($this->formats[$format])) { + $formatMatcher = new FormatMatcher($formats); + $format = $formatMatcher->getFormat($contentType); + if (null === $format) { $supportedMimeTypes = []; - foreach ($this->formats as $mimeTypes) { + foreach ($formats as $mimeTypes) { foreach ($mimeTypes as $mimeType) { $supportedMimeTypes[] = $mimeType; } diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index c5b37f7f206..be34b555f3a 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -137,7 +137,7 @@ public function getResourceObjectTypeFields(?string $resourceClass, ResourceMeta $idField = ['type' => GraphQLType::nonNull(GraphQLType::id())]; $clientMutationId = GraphQLType::string(); - if (null !== $ioMetadata && null === $ioMetadata['class']) { + if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null === $ioMetadata['class']) { if ($input) { return ['clientMutationId' => $clientMutationId]; } diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index 2479adadcfe..7a1e0278483 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -50,8 +50,12 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup private $subresourceOperationFactory; private $nameConverter; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator, SubresourceOperationFactoryInterface $subresourceOperationFactory = null, NameConverterInterface $nameConverter = null) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver = null, UrlGeneratorInterface $urlGenerator, SubresourceOperationFactoryInterface $subresourceOperationFactory = null, NameConverterInterface $nameConverter = null) { + if ($operationMethodResolver) { + @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), E_USER_DEPRECATED); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; @@ -245,12 +249,16 @@ private function getHydraOperations(string $resourceClass, ResourceMetadata $res */ private function getHydraOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, string $operationType, SubresourceMetadata $subresourceMetadata = null): array { - if (OperationType::COLLECTION === $operationType) { - $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); - } elseif (OperationType::ITEM === $operationType) { - $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); + if ($this->operationMethodResolver) { + if (OperationType::COLLECTION === $operationType) { + $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); + } elseif (OperationType::ITEM === $operationType) { + $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); + } else { + $method = 'GET'; + } } else { - $method = 'GET'; + $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); } $hydraOperation = $operation['hydra_context'] ?? []; diff --git a/src/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php b/src/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php new file mode 100644 index 00000000000..24cca24c2b3 --- /dev/null +++ b/src/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Metadata\Resource\Factory; + +use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Exception\ResourceClassNotFoundException; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; + +/** + * Normalizes enabled formats. + * + * Formats hierarchy: + * * resource formats + * * resource input/output formats + * * operation formats + * * operation input/output formats + * + * @author Kévin Dunglas + */ +final class FormatsResourceMetadataFactory implements ResourceMetadataFactoryInterface +{ + private $decorated; + private $formats; + private $patchFormats; + + public function __construct(ResourceMetadataFactoryInterface $decorated, array $formats, array $patchFormats) + { + $this->decorated = $decorated; + $this->formats = $formats; + $this->patchFormats = $patchFormats; + } + + /** + * Adds the formats attributes. + * + * @see OperationResourceMetadataFactory + * + * @throws ResourceClassNotFoundException + */ + public function create(string $resourceClass): ResourceMetadata + { + $resourceMetadata = $this->decorated->create($resourceClass); + $rawResourceFormats = $resourceMetadata->getAttribute('formats'); + $resourceFormats = null === $rawResourceFormats ? $this->formats : $this->normalizeFormats($rawResourceFormats); + + $rawResourceInputFormats = $resourceMetadata->getAttribute('input_formats'); + $rawResourceOutputFormats = $resourceMetadata->getAttribute('output_formats'); + + $resourceInputFormats = $rawResourceInputFormats ? $this->normalizeFormats($rawResourceInputFormats) : $resourceFormats; + $resourceOutputFormats = $rawResourceOutputFormats ? $this->normalizeFormats($rawResourceOutputFormats) : $resourceFormats; + + if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { + $resourceMetadata = $resourceMetadata->withCollectionOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $collectionOperations)); + } + + if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { + $resourceMetadata = $resourceMetadata->withItemOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $itemOperations)); + } + + if (null !== $subresourceOperations = $resourceMetadata->getSubresourceOperations()) { + $resourceMetadata = $resourceMetadata->withSubresourceOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $subresourceOperations)); + } + + return $resourceMetadata; + } + + private function normalize(array $resourceInputFormats, array $resourceOutputFormats, array $operations): array + { + $newOperations = []; + foreach ($operations as $operationName => $operation) { + if ('PATCH' === ($operation['method'] ?? '') && !isset($operation['formats']) && !isset($operation['input_formats'])) { + $operation['input_formats'] = $this->patchFormats; + } + + if (isset($operation['formats'])) { + $operation['formats'] = $this->normalizeFormats($operation['formats']); + } + + $operation['input_formats'] = isset($operation['input_formats']) ? $this->normalizeFormats($operation['input_formats']) : $operation['formats'] ?? $resourceInputFormats; + $operation['output_formats'] = isset($operation['output_formats']) ? $this->normalizeFormats($operation['output_formats']) : $operation['formats'] ?? $resourceOutputFormats; + + $newOperations[$operationName] = $operation; + } + + return $newOperations; + } + + /** + * @param array|string $currentFormats + * + * @throws InvalidArgumentException + */ + private function normalizeFormats($currentFormats): array + { + $currentFormats = (array) $currentFormats; + + $normalizedFormats = []; + foreach ($currentFormats as $format => $value) { + if (!is_numeric($format)) { + $normalizedFormats[$format] = (array) $value; + continue; + } + if (!\is_string($value)) { + throw new InvalidArgumentException(sprintf("The 'formats' attributes value must be a string when trying to include an already configured format, %s given.", \gettype($value))); + } + if (\array_key_exists($value, $this->formats)) { + $normalizedFormats[$value] = $this->formats[$value]; + continue; + } + + throw new InvalidArgumentException(sprintf("You either need to add the format '%s' to your project configuration or declare a mime type for it in your annotation.", $value)); + } + + return $normalizedFormats; + } +} diff --git a/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php b/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php index c5809ffb0c6..24e7b997d72 100644 --- a/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php +++ b/src/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php @@ -100,7 +100,7 @@ private function transformInputOutput($attribute): ?array $attribute = ['class' => $attribute]; } - if (!isset($attribute['name'])) { + if (!isset($attribute['name']) && isset($attribute['class'])) { $attribute['name'] = (new \ReflectionClass($attribute['class']))->getShortName(); } diff --git a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php index dba6d6709df..8a12f68c3d8 100644 --- a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php +++ b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php @@ -36,16 +36,17 @@ final class OperationResourceMetadataFactory implements ResourceMetadataFactoryI public const SUPPORTED_ITEM_OPERATION_METHODS = [ 'GET' => true, 'PUT' => true, + // PATCH is automatically supported if at least one patch format has been configured 'DELETE' => true, ]; private $decorated; - private $formats; + private $patchFormats; - public function __construct(ResourceMetadataFactoryInterface $decorated, array $formats = []) + public function __construct(ResourceMetadataFactoryInterface $decorated, array $patchFormats = []) { $this->decorated = $decorated; - $this->formats = $formats; + $this->patchFormats = $patchFormats; } /** @@ -58,11 +59,9 @@ public function create(string $resourceClass): ResourceMetadata $collectionOperations = $resourceMetadata->getCollectionOperations(); if (null === $collectionOperations) { - $resourceMetadata = $resourceMetadata->withCollectionOperations($this->createOperations( - $isAbstract ? ['GET'] : ['GET', 'POST'] - )); + $resourceMetadata = $resourceMetadata->withCollectionOperations($this->createOperations($isAbstract ? ['GET'] : ['GET', 'POST'])); } else { - $resourceMetadata = $this->normalize(true, $resourceMetadata, $collectionOperations); + $resourceMetadata = $this->normalize(true, $resourceClass, $resourceMetadata, $collectionOperations); } $itemOperations = $resourceMetadata->getItemOperations(); @@ -72,14 +71,14 @@ public function create(string $resourceClass): ResourceMetadata if (!$isAbstract) { $methods[] = 'PUT'; - if (isset($this->formats['jsonapi'])) { + if ($this->patchFormats) { $methods[] = 'PATCH'; } } $resourceMetadata = $resourceMetadata->withItemOperations($this->createOperations($methods)); } else { - $resourceMetadata = $this->normalize(false, $resourceMetadata, $itemOperations); + $resourceMetadata = $this->normalize(false, $resourceClass, $resourceMetadata, $itemOperations); } $graphql = $resourceMetadata->getGraphql(); @@ -102,7 +101,7 @@ private function createOperations(array $methods): array return $operations; } - private function normalize(bool $collection, ResourceMetadata $resourceMetadata, array $operations): ResourceMetadata + private function normalize(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, array $operations): ResourceMetadata { $newOperations = []; foreach ($operations as $operationName => $operation) { @@ -116,11 +115,16 @@ private function normalize(bool $collection, ResourceMetadata $resourceMetadata, if ($collection) { $supported = isset(self::SUPPORTED_COLLECTION_OPERATION_METHODS[$upperOperationName]); } else { - $supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || (isset($this->formats['jsonapi']) && 'PATCH' === $upperOperationName); + $supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || ($this->patchFormats && 'PATCH' === $upperOperationName); } if (!isset($operation['method']) && !isset($operation['route_name'])) { - $supported ? $operation['method'] = $upperOperationName : $operation['route_name'] = $operationName; + if ($supported) { + $operation['method'] = $upperOperationName; + } else { + @trigger_error(sprintf('The "route_name" attribute will not be set automatically again in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', $collection ? 'collection' : 'item', $operationName, $resourceClass), E_USER_DEPRECATED); + $operation['route_name'] = $operationName; + } } if (isset($operation['method'])) { diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index 11b7eb01360..bd14625394d 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -28,10 +28,12 @@ final class SerializerContextBuilder implements SerializerContextBuilderInterface { private $resourceMetadataFactory; + private $patchFormats; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $patchFormats = []) { $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->patchFormats = $patchFormats; } /** @@ -65,8 +67,8 @@ public function createFromRequest(Request $request, bool $normalization, array $ } $context['resource_class'] = $attributes['resource_class']; - $context['input'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'input', $resourceMetadata->getAttribute('input')); - $context['output'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'output', $resourceMetadata->getAttribute('output')); + $context['input'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'input', null, true); + $context['output'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'output', null, true); $context['request_uri'] = $request->getRequestUri(); $context['uri'] = $request->getUri(); @@ -89,6 +91,14 @@ public function createFromRequest(Request $request, bool $normalization, array $ unset($context[DocumentationNormalizer::SWAGGER_DEFINITION_NAME]); + if ( + isset($this->patchFormats['json']) + && !isset($context['skip_null_values']) + && \in_array('application/merge-patch+json', $this->patchFormats['json'], true) + ) { + $context['skip_null_values'] = true; + } + return $context; } } diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index bc17577dd03..5105ab5f204 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -15,6 +15,7 @@ use ApiPlatform\Core\Api\FilterCollection; use ApiPlatform\Core\Api\FilterLocatorTrait; +use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface; use ApiPlatform\Core\Api\OperationMethodResolverInterface; use ApiPlatform\Core\Api\OperationType; @@ -81,6 +82,7 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup private $itemsPerPageParameterName; private $paginationClientEnabled; private $paginationClientEnabledParameterName; + private $formats; private $formatsProvider; private $defaultContext = [ self::BASE_URL => '/', @@ -89,13 +91,25 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup ]; /** - * @param ContainerInterface|FilterCollection|null $filterLocator The new filter locator or the deprecated filter collection + * @param ContainerInterface|FilterCollection|null $filterLocator The new filter locator or the deprecated filter collection + * @param array|OperationAwareFormatsProviderInterface $formats */ - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', OperationAwareFormatsProviderInterface $formatsProvider = null, bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = []) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver = null, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', $formats = [], bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = []) { if ($urlGenerator) { @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.1 and will be removed in 3.0.', UrlGeneratorInterface::class, __METHOD__), E_USER_DEPRECATED); } + if ($operationMethodResolver) { + @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), E_USER_DEPRECATED); + } + + if ($formats instanceof FormatsProviderInterface) { + @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0, pass an array instead.', FormatsProviderInterface::class, __METHOD__), E_USER_DEPRECATED); + + $this->formatsProvider = $formats; + } else { + $this->formats = $formats; + } $this->setFilterLocator($filterLocator, true); @@ -116,10 +130,8 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa $this->paginationEnabled = $paginationEnabled; $this->paginationPageParameterName = $paginationPageParameterName; $this->apiKeys = $apiKeys; - $this->subresourceOperationFactory = $subresourceOperationFactory; $this->clientItemsPerPage = $clientItemsPerPage; $this->itemsPerPageParameterName = $itemsPerPageParameterName; - $this->formatsProvider = $formatsProvider; $this->paginationClientEnabled = $paginationClientEnabled; $this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName; @@ -133,7 +145,6 @@ public function normalize($object, $format = null, array $context = []) { $v3 = 3 === ($context['spec_version'] ?? $this->defaultContext['spec_version']) && !($context['api_gateway'] ?? $this->defaultContext['api_gateway']); - $mimeTypes = $object->getMimeTypes(); $definitions = new \ArrayObject(); $paths = new \ArrayObject(); $links = new \ArrayObject(); @@ -143,15 +154,15 @@ public function normalize($object, $format = null, array $context = []) $resourceShortName = $resourceMetadata->getShortName(); // Items needs to be parsed first to be able to reference the lines from the collection operation - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::ITEM, $links); - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::COLLECTION, $links); + $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::ITEM, $links); + $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::COLLECTION, $links); if (null === $this->subresourceOperationFactory) { continue; } foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $subresourceOperation) { - $paths[$this->getPath($subresourceOperation['shortNames'][0], $subresourceOperation['route_name'], $subresourceOperation, OperationType::SUBRESOURCE)] = $this->addSubresourceOperation($v3, $subresourceOperation, $definitions, $operationId, $mimeTypes, $resourceClass, $resourceMetadata); + $paths[$this->getPath($subresourceOperation['shortNames'][0], $subresourceOperation['route_name'], $subresourceOperation, OperationType::SUBRESOURCE)] = $this->addSubresourceOperation($v3, $subresourceOperation, $definitions, $operationId, $resourceMetadata); } } @@ -164,7 +175,7 @@ public function normalize($object, $format = null, array $context = []) /** * Updates the list of entries in the paths collection. */ - private function addPaths(bool $v3, \ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, array $mimeTypes, string $operationType, \ArrayObject $links) + private function addPaths(bool $v3, \ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, string $operationType, \ArrayObject $links) { if (null === $operations = OperationType::COLLECTION === $operationType ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) { return; @@ -172,9 +183,13 @@ private function addPaths(bool $v3, \ArrayObject $paths, \ArrayObject $definitio foreach ($operations as $operationName => $operation) { $path = $this->getPath($resourceShortName, $operationName, $operation, $operationType); - $method = OperationType::ITEM === $operationType ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); + if ($this->operationMethodResolver) { + $method = OperationType::ITEM === $operationType ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); + } else { + $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); + } - $paths[$path][strtolower($method)] = $this->getPathOperation($v3, $operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $mimeTypes, $definitions, $links); + $paths[$path][strtolower($method)] = $this->getPathOperation($v3, $operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $definitions, $links); } } @@ -200,10 +215,8 @@ private function getPath(string $resourceShortName, string $operationName, array * Gets a path Operation Object. * * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object - * - * @param string[] $mimeTypes */ - private function getPathOperation(bool $v3, string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, array $mimeTypes, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject + private function getPathOperation(bool $v3, string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject { $pathOperation = new \ArrayObject($operation[$v3 ? 'openapi_context' : 'swagger_context'] ?? []); $resourceShortName = $resourceMetadata->getShortName(); @@ -215,20 +228,26 @@ private function getPathOperation(bool $v3, string $operationName, array $operat if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { $pathOperation['deprecated'] = true; } - if (null !== $this->formatsProvider) { - $responseFormats = $this->formatsProvider->getFormatsFromOperation($resourceClass, $operationName, $operationType); - $responseMimeTypes = $this->extractMimeTypes($responseFormats); + + if (null === $this->formatsProvider) { + $requestFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input_formats', [], true); + $responseFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output_formats', [], true); + } else { + $requestFormats = $responseFormats = $this->formatsProvider->getFormatsFromOperation($resourceClass, $operationName, $operationType); } + + $requestMimeTypes = $this->extractMimeTypes($requestFormats); + $responseMimeTypes = $this->extractMimeTypes($responseFormats); switch ($method) { case 'GET': - return $this->updateGetOperation($v3, $pathOperation, $responseMimeTypes ?? $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); + return $this->updateGetOperation($v3, $pathOperation, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); case 'POST': - return $this->updatePostOperation($v3, $pathOperation, $responseMimeTypes ?? $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions, $links); + return $this->updatePostOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions, $links); case 'PATCH': $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName); // no break case 'PUT': - return $this->updatePutOperation($v3, $pathOperation, $responseMimeTypes ?? $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); + return $this->updatePutOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); case 'DELETE': return $this->updateDeleteOperation($v3, $pathOperation, $resourceShortName, $operationType, $operationName, $resourceMetadata); } @@ -342,7 +361,7 @@ private function addPaginationParameters(bool $v3, ResourceMetadata $resourceMet /** * @throws ResourceClassNotFoundException */ - private function addSubresourceOperation(bool $v3, array $subresourceOperation, \ArrayObject $definitions, string $operationId, array $mimeTypes, string $resourceClass, ResourceMetadata $resourceMetadata): \ArrayObject + private function addSubresourceOperation(bool $v3, array $subresourceOperation, \ArrayObject $definitions, string $operationId, ResourceMetadata $resourceMetadata): \ArrayObject { $operationName = 'get'; // TODO: we might want to extract that at some point to also support other subresource operations @@ -354,16 +373,24 @@ private function addSubresourceOperation(bool $v3, array $subresourceOperation, $pathOperation['operationId'] = $operationId; $pathOperation['summary'] = sprintf('Retrieves %s%s resource%s.', $subresourceOperation['collection'] ? 'the collection of ' : 'a ', $subresourceOperation['shortNames'][0], $subresourceOperation['collection'] ? 's' : ''); - if (null !== $this->formatsProvider) { + if (null === $this->formatsProvider) { + // TODO: Subresource operation metadata aren't available by default, for now we have to fallback on default formats. + // TODO: A better approach would be to always populate the subresource operation array. + $responseFormats = $this + ->resourceMetadataFactory + ->create($subresourceOperation['resource_class']) + ->getTypedOperationAttribute(OperationType::SUBRESOURCE, $operationName, 'output_formats', $this->formats, true); + } else { $responseFormats = $this->formatsProvider->getFormatsFromOperation($subresourceOperation['resource_class'], $operationName, OperationType::SUBRESOURCE); - $responseMimeTypes = $this->extractMimeTypes($responseFormats); } + $mimeTypes = $this->extractMimeTypes($responseFormats); + if (!$v3) { - $pathOperation['produces'] = $responseMimeTypes ?? $mimeTypes; + $pathOperation['produces'] = $mimeTypes; } - $pathOperation['responses'] = $this->getSubresourceResponse($v3, $responseMimeTypes ?? $mimeTypes, $subresourceOperation['collection'], $subresourceOperation['shortNames'][0], $responseDefinitionKey); + $pathOperation['responses'] = $this->getSubresourceResponse($v3, $mimeTypes, $subresourceOperation['collection'], $subresourceOperation['shortNames'][0], $responseDefinitionKey); // Avoid duplicates parameters when there is a filter on a subresource identifier $parametersMemory = []; $pathOperation['parameters'] = []; @@ -417,11 +444,11 @@ private function getSubresourceResponse(bool $v3, $mimeTypes, bool $collection, return ['200' => $okResponse, '404' => ['description' => 'Resource not found']]; } - private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject + private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject { if (!$v3) { - $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; - $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; + $pathOperation['consumes'] ?? $pathOperation['consumes'] = $requestMimeTypes; + $pathOperation['produces'] ?? $pathOperation['produces'] = $responseMimeTypes; } $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName); @@ -440,7 +467,7 @@ private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, arra $successResponse = ['description' => sprintf('%s resource created', $resourceShortName)]; if ($responseDefinitionKey) { if ($v3) { - $successResponse['content'] = array_fill_keys($mimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $responseDefinitionKey)]]); + $successResponse['content'] = array_fill_keys($responseMimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $responseDefinitionKey)]]); if ($links[$key = 'get'.ucfirst($resourceShortName).ucfirst(OperationType::ITEM)] ?? null) { $successResponse['links'] = [ucfirst($key) => $links[$key]]; } @@ -463,7 +490,7 @@ private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, arra $requestDefinitionKey = $this->getDefinition($v3, $definitions, $resourceMetadata, $resourceClass, $inputClass, $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName)); if ($v3) { $pathOperation['requestBody'] ?? $pathOperation['requestBody'] = [ - 'content' => array_fill_keys($mimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $requestDefinitionKey)]]), + 'content' => array_fill_keys($requestMimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $requestDefinitionKey)]]), 'description' => sprintf('The new %s resource', $resourceShortName), ]; } else { @@ -478,11 +505,11 @@ private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, arra return $pathOperation; } - private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject + private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject { if (!$v3) { - $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; - $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; + $pathOperation['consumes'] ?? $pathOperation['consumes'] = $requestMimeTypes; + $pathOperation['produces'] ?? $pathOperation['produces'] = $responseMimeTypes; } $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName); @@ -498,7 +525,7 @@ private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $successResponse = ['description' => sprintf('%s resource updated', $resourceShortName)]; if ($responseDefinitionKey) { if ($v3) { - $successResponse['content'] = array_fill_keys($mimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $responseDefinitionKey)]]); + $successResponse['content'] = array_fill_keys($responseMimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $responseDefinitionKey)]]); } else { $successResponse['schema'] = ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)]; } @@ -518,7 +545,7 @@ private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $requestDefinitionKey = $this->getDefinition($v3, $definitions, $resourceMetadata, $resourceClass, $inputClass, $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName)); if ($v3) { $pathOperation['requestBody'] ?? $pathOperation['requestBody'] = [ - 'content' => array_fill_keys($mimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $requestDefinitionKey)]]), + 'content' => array_fill_keys($requestMimeTypes, ['schema' => ['$ref' => sprintf('#/components/schemas/%s', $requestDefinitionKey)]]), 'description' => sprintf('The updated %s resource', $resourceShortName), ]; } else { diff --git a/tests/Api/FormatsProviderTest.php b/tests/Api/FormatsProviderTest.php index e95ace74282..ede81b9b582 100644 --- a/tests/Api/FormatsProviderTest.php +++ b/tests/Api/FormatsProviderTest.php @@ -21,6 +21,8 @@ /** * @author Anthony GRASSIOT + * + * @group legacy */ class FormatsProviderTest extends TestCase { diff --git a/tests/Bridge/Symfony/Bundle/Action/SwaggerUiActionTest.php b/tests/Bridge/Symfony/Bundle/Action/SwaggerUiActionTest.php index d676636b0cc..f834557521d 100644 --- a/tests/Bridge/Symfony/Bundle/Action/SwaggerUiActionTest.php +++ b/tests/Bridge/Symfony/Bundle/Action/SwaggerUiActionTest.php @@ -145,6 +145,7 @@ public function testDoNotRunCurrentRequest(Request $request) $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['Foo', 'Bar']))->shouldBeCalled(); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata()); $normalizerProphecy = $this->prophesize(NormalizerInterface::class); $normalizerProphecy->normalize(Argument::type(Documentation::class), 'json', Argument::type('array'))->willReturn(self::SPEC)->shouldBeCalled(); @@ -187,14 +188,11 @@ public function testDoNotRunCurrentRequest(Request $request) $action($request); } - public function getDoNotRunCurrentRequestParameters() + public function getDoNotRunCurrentRequestParameters(): iterable { $nonSafeRequest = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post']); $nonSafeRequest->setMethod('POST'); - - return [ - [$nonSafeRequest], - [new Request()], - ]; + yield [$nonSafeRequest]; + yield [new Request()]; } } diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index b896ce4da6d..e3b5c5b9af4 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -765,6 +765,7 @@ private function getPartialContainerBuilderProphecy() 'api_platform.description' => 'description', 'api_platform.error_formats' => ['jsonproblem' => ['application/problem+json'], 'jsonld' => ['application/ld+json']], 'api_platform.formats' => ['jsonld' => ['application/ld+json'], 'jsonhal' => ['application/hal+json']], + 'api_platform.patch_formats' => [], 'api_platform.exception_to_status' => [ ExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, @@ -865,6 +866,7 @@ private function getPartialContainerBuilderProphecy() 'api_platform.metadata.property.name_collection_factory.xml', 'api_platform.metadata.resource.metadata_factory.cached', 'api_platform.metadata.resource.metadata_factory.operation', + 'api_platform.metadata.resource.metadata_factory.formats', 'api_platform.metadata.resource.metadata_factory.input_output', 'api_platform.metadata.resource.metadata_factory.short_name', 'api_platform.metadata.resource.metadata_factory.xml', diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 45523796686..976cfd51a1a 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -90,6 +90,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm 'json' => ['mime_types' => ['application/json']], 'html' => ['mime_types' => ['text/html']], ], + 'patch_formats' => [], 'error_formats' => [ 'jsonproblem' => ['mime_types' => ['application/problem+json']], 'jsonld' => ['mime_types' => ['application/ld+json']], diff --git a/tests/Documentation/Action/DocumentationActionTest.php b/tests/Documentation/Action/DocumentationActionTest.php index 6348022a915..3d7bbd3efe7 100644 --- a/tests/Documentation/Action/DocumentationActionTest.php +++ b/tests/Documentation/Action/DocumentationActionTest.php @@ -29,7 +29,27 @@ */ class DocumentationActionTest extends TestCase { - public function testDocumentationAction() + public function testyDocumentationAction(): void + { + $requestProphecy = $this->prophesize(Request::class); + $attributesProphecy = $this->prophesize(ParameterBagInterface::class); + $queryProphecy = $this->prophesize(ParameterBag::class); + $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); + $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummies'])); + $requestProphecy->attributes = $attributesProphecy->reveal(); + $requestProphecy->query = $queryProphecy->reveal(); + $requestProphecy->getBaseUrl()->willReturn('/api')->shouldBeCalledTimes(1); + $queryProphecy->getBoolean('api_gateway')->willReturn(true)->shouldBeCalledTimes(1); + $queryProphecy->getInt('spec_version', 2)->willReturn(2)->shouldBeCalledTimes(1); + $attributesProphecy->all()->willReturn(['_api_normalization_context' => ['foo' => 'bar', 'base_url' => '/api', 'api_gateway' => true, 'spec_version' => 2]])->shouldBeCalledTimes(1); + $attributesProphecy->get('_api_normalization_context', [])->willReturn(['foo' => 'bar'])->shouldBeCalledTimes(1); + $attributesProphecy->set('_api_normalization_context', ['foo' => 'bar', 'base_url' => '/api', 'api_gateway' => true, 'spec_version' => 2])->shouldBeCalledTimes(1); + + $documentation = new DocumentationAction($resourceNameCollectionFactoryProphecy->reveal(), 'My happy hippie api', 'lots of chocolate', '1.0.0'); + $this->assertEquals(new Documentation(new ResourceNameCollection(['dummies']), 'My happy hippie api', 'lots of chocolate', '1.0.0'), $documentation($requestProphecy->reveal())); + } + + public function testLegacyDocumentationAction(): void { $requestProphecy = $this->prophesize(Request::class); $attributesProphecy = $this->prophesize(ParameterBagInterface::class); @@ -53,7 +73,7 @@ public function testDocumentationAction() /** * @group legacy - * @expectedDeprecation Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3 + * @expectedDeprecation Passing an array or an instance of "ApiPlatform\Core\Api\FormatsProviderInterface" as 5th parameter of the constructor of "ApiPlatform\Core\Documentation\Action\DocumentationAction" is deprecated since API Platform 2.5 */ public function testDocumentationActionFormatDeprecation() { @@ -61,14 +81,4 @@ public function testDocumentationActionFormatDeprecation() $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummies'])); new DocumentationAction($resourceNameCollectionFactoryProphecy->reveal(), '', '', '', ['formats' => ['jsonld' => 'application/ld+json']]); } - - public function testDocumentationActionThrowsOnBadFormatArgument() - { - $this->expectException(\ApiPlatform\Core\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$formatsProvider" argument is expected to be an implementation of the "ApiPlatform\\Core\\Api\\FormatsProviderInterface" interface.'); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummies'])); - new DocumentationAction($resourceNameCollectionFactoryProphecy->reveal(), '', '', '', 'foo'); - } } diff --git a/tests/EventListener/AddFormatListenerTest.php b/tests/EventListener/AddFormatListenerTest.php index 480b1124c75..e3c0b636b57 100644 --- a/tests/EventListener/AddFormatListenerTest.php +++ b/tests/EventListener/AddFormatListenerTest.php @@ -15,6 +15,8 @@ use ApiPlatform\Core\Api\FormatsProviderInterface; use ApiPlatform\Core\EventListener\AddFormatListener; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use Negotiation\Negotiator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -36,7 +38,7 @@ public function testNoResourceClass() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $formatsProviderProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); $listener->onKernelRequest($event); @@ -44,7 +46,29 @@ public function testNoResourceClass() $this->assertNull($request->getRequestFormat(null)); } - public function testSupportedRequestFormat() + public function testSupportedRequestFormat(): void + { + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['xml' => ['text/xml']]]] + )); + + $this->doTestSupportedRequestFormat($resourceMetadataFactory->reveal()); + } + + public function testLegacySupportedRequestFormat(): void + { + $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['xml' => ['text/xml']]); + + $this->doTestSupportedRequestFormat($formatsProviderProphecy->reveal()); + } + + private function doTestSupportedRequestFormat($resourceMetadataFactory): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->setRequestFormat('xml'); @@ -53,17 +77,14 @@ public function testSupportedRequestFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['xml' => ['text/xml']]); - - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory); $listener->onKernelRequest($event); $this->assertSame('xml', $request->getRequestFormat()); $this->assertSame('text/xml', $request->getMimeType($request->getRequestFormat())); } - public function testRespondFlag() + public function testRespondFlag(): void { $request = new Request([], [], ['_api_respond' => true]); $request->setRequestFormat('xml'); @@ -72,17 +93,16 @@ public function testRespondFlag() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['xml' => ['text/xml']]); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory->reveal(), ['xml' => ['text/xml']]); $listener->onKernelRequest($event); $this->assertSame('xml', $request->getRequestFormat()); $this->assertSame('text/xml', $request->getMimeType($request->getRequestFormat())); } - public function testUnsupportedRequestFormat() + public function testUnsupportedRequestFormat(): void { $this->expectException(NotAcceptableHttpException::class); $this->expectExceptionMessage('Requested format "text/xml" is not supported. Supported MIME types are "application/json".'); @@ -94,16 +114,22 @@ public function testUnsupportedRequestFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['json' => ['application/json']]); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($event); $this->assertSame('json', $request->getRequestFormat()); } - public function testSupportedAcceptHeader() + public function testSupportedAcceptHeader(): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->headers->set('Accept', 'text/html, application/xhtml+xml, application/xml, application/json;q=0.9, */*;q=0.8'); @@ -112,16 +138,25 @@ public function testSupportedAcceptHeader() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['binary' => ['application/octet-stream'], 'json' => ['application/json']]); - - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => [ + 'binary' => ['application/octet-stream'], + 'json' => ['application/json'], ], + ]] + )); + + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($event); $this->assertSame('json', $request->getRequestFormat()); } - public function testSupportedAcceptHeaderSymfonyBuiltInFormat() + public function testSupportedAcceptHeaderSymfonyBuiltInFormat(): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->headers->set('Accept', 'application/json'); @@ -130,16 +165,22 @@ public function testSupportedAcceptHeaderSymfonyBuiltInFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['jsonld' => ['application/ld+json', 'application/json']]); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['jsonld' => ['application/ld+json', 'application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($event); $this->assertSame('jsonld', $request->getRequestFormat()); } - public function testAcceptAllHeader() + public function testAcceptAllHeader(): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->headers->set('Accept', 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8'); @@ -148,17 +189,23 @@ public function testAcceptAllHeader() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['binary' => ['application/octet-stream'], 'json' => ['application/json']]); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['binary' => ['application/octet-stream'], 'json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($event); $this->assertSame('binary', $request->getRequestFormat()); $this->assertSame('application/octet-stream', $request->getMimeType($request->getRequestFormat())); } - public function testUnsupportedAcceptHeader() + public function testUnsupportedAcceptHeader(): void { $this->expectException(NotAcceptableHttpException::class); $this->expectExceptionMessage('Requested format "text/html, application/xhtml+xml, application/xml;q=0.9" is not supported. Supported MIME types are "application/octet-stream", "application/json".'); @@ -170,14 +217,20 @@ public function testUnsupportedAcceptHeader() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['binary' => ['application/octet-stream'], 'json' => ['application/json']]); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['binary' => ['application/octet-stream'], 'json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); } - public function testUnsupportedAcceptHeaderSymfonyBuiltInFormat() + public function testUnsupportedAcceptHeaderSymfonyBuiltInFormat(): void { $this->expectException(NotAcceptableHttpException::class); $this->expectExceptionMessage('Requested format "text/xml" is not supported. Supported MIME types are "application/json".'); @@ -189,14 +242,20 @@ public function testUnsupportedAcceptHeaderSymfonyBuiltInFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['json' => ['application/json']]); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); } - public function testInvalidAcceptHeader() + public function testInvalidAcceptHeader(): void { $this->expectException(NotAcceptableHttpException::class); $this->expectExceptionMessage('Requested format "invalid" is not supported. Supported MIME types are "application/json".'); @@ -208,14 +267,20 @@ public function testInvalidAcceptHeader() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['json' => ['application/json']]); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); } - public function testAcceptHeaderTakePrecedenceOverRequestFormat() + public function testAcceptHeaderTakePrecedenceOverRequestFormat(): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->headers->set('Accept', 'application/json'); @@ -225,16 +290,22 @@ public function testAcceptHeaderTakePrecedenceOverRequestFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['xml' => ['application/xml'], 'json' => ['application/json']]); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + [], + ['get' => ['output_formats' => ['xml' => ['application/xml'], 'json' => ['application/json']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); $this->assertSame('json', $request->getRequestFormat()); } - public function testInvalidRouteFormat() + public function testInvalidRouteFormat(): void { $this->expectException(NotFoundHttpException::class); $this->expectExceptionMessage('Format "invalid" is not supported'); @@ -245,14 +316,21 @@ public function testInvalidRouteFormat() $eventProphecy->getRequest()->willReturn($request); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::any())->willReturn(['json' => ['application/json']]); - - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + null, + ['formats' => ['json' => ['application/json']]] + )); + + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); } - public function testResourceClassSupportedRequestFormat() + public function testResourceClassSupportedRequestFormat(): void { $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get']); $request->setRequestFormat('csv'); @@ -261,36 +339,35 @@ public function testResourceClassSupportedRequestFormat() $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); $event = $eventProphecy->reveal(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes([ - 'resource_class' => 'Foo', - 'collection_operation_name' => 'get', - 'receive' => true, - 'respond' => true, - 'persist' => true, - ])->willReturn(['csv' => ['text/csv']])->shouldBeCalled(); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['get' => ['output_formats' => ['csv' => ['text/csv']]]] + )); - $listener = new AddFormatListener(new Negotiator(), $formatsProviderProphecy->reveal()); + $listener = new AddFormatListener(new Negotiator(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($event); $this->assertSame('csv', $request->getRequestFormat()); $this->assertSame('text/csv', $request->getMimeType($request->getRequestFormat())); } - public function testBadFormatsProviderParameterThrowsException() - { - $this->expectException(\ApiPlatform\Core\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$formatsProvider" argument is expected to be an implementation of the "ApiPlatform\\Core\\Api\\FormatsProviderInterface" interface.'); - - new AddFormatListener(new Negotiator(), 'foo'); - } - /** * @group legacy - * @expectedDeprecation Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3 + * @expectedDeprecation Passing an array or an instance of "ApiPlatform\Core\Api\FormatsProviderInterface" as 2nd parameter of the constructor of "ApiPlatform\Core\EventListener\AddFormatListener" is deprecated since API Platform 2.5, pass an instance of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface" instead + * @dataProvider legacyFormatsProvider */ - public function testLegacyFormatsParameter() + public function testLegacyFormatsParameter($formatsProvider): void + { + new AddFormatListener(new Negotiator(), $formatsProvider); + } + + public function legacyFormatsProvider(): iterable { - new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']]); + yield [['xml' => ['text/xml']]]; + yield [$this->prophesize(FormatsProviderInterface::class)->reveal()]; } } diff --git a/tests/EventListener/DeserializeListenerTest.php b/tests/EventListener/DeserializeListenerTest.php index 516661ff26a..74b1c3421d5 100644 --- a/tests/EventListener/DeserializeListenerTest.php +++ b/tests/EventListener/DeserializeListenerTest.php @@ -32,9 +32,9 @@ */ class DeserializeListenerTest extends TestCase { - public const FORMATS = ['json' => ['application/json']]; + private const FORMATS = ['json' => ['application/json']]; - public function testDoNotCallWhenRequestMethodIsSafe() + public function testDoNotCallWhenRequestMethodIsSafe(): void { $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -48,14 +48,14 @@ public function testDoNotCallWhenRequestMethodIsSafe() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled(); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create()->shouldNotBeCalled(); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testDoNotCallWhenRequestNotManaged() + public function testDoNotCallWhenRequestNotManaged(): void { $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -69,21 +69,22 @@ public function testDoNotCallWhenRequestNotManaged() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled(); + $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactory->create()->shouldNotBeCalled(); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactory->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testDoNotDeserializeWhenReceiveFlagIsFalse() + public function testDoNotDeserializeWhenReceiveFlagIsFalse(): void { $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create()->shouldNotBeCalled(); $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); $request->setMethod('POST'); @@ -91,20 +92,17 @@ public function testDoNotDeserializeWhenReceiveFlagIsFalse() $eventProphecy = $this->prophesize(GetResponseEvent::class); $eventProphecy->getRequest()->willReturn($request); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testDoNotDeserializeWhenDisabledInOperationAttribute() + public function testDoNotDeserializeWhenDisabledInOperationAttribute(): void { $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->deserialize(Argument::cetera())->shouldNotBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array')); - $resourceMetadata = new ResourceMetadata('Dummy', null, null, [], [ 'post' => [ 'deserialize' => false, @@ -120,14 +118,39 @@ public function testDoNotDeserializeWhenDisabledInOperationAttribute() $eventProphecy = $this->prophesize(GetResponseEvent::class); $eventProphecy->getRequest()->willReturn($request); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); $listener->onKernelRequest($eventProphecy->reveal()); } /** * @dataProvider methodProvider */ - public function testDeserialize(string $method, bool $populateObject) + public function testDeserialize(string $method, bool $populateObject): void + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + ['put' => ['input_formats' => self::FORMATS]], + ['post' => ['input_formats' => self::FORMATS]] + )); + + $this->doTestDeserialize($method, $populateObject, $resourceMetadataFactoryProphecy->reveal()); + } + + /** + * @dataProvider methodProvider + */ + public function legacyTestDeserialize(string $method, bool $populateObject): void + { + $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(self::FORMATS)->shouldBeCalled(); + + $this->doTestDeserialize($method, $populateObject, $formatsProviderProphecy->reveal()); + } + + private function doTestDeserialize(string $method, bool $populateObject, $resourceMetadataFactory): void { $result = $populateObject ? new \stdClass() : null; $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -144,20 +167,48 @@ public function testDeserialize(string $method, bool $populateObject) $context['resource_class'] = 'Foo'; $serializerProphecy->deserialize('{}', 'Foo', 'json', $context)->willReturn($result)->shouldBeCalled(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(self::FORMATS)->shouldBeCalled(); - $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo'], 'resource_class' => 'Foo'])->shouldBeCalled(); - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactory); $listener->onKernelRequest($eventProphecy->reveal()); } /** * @dataProvider methodProvider */ - public function testDeserializeResourceClassSupportedFormat(string $method, bool $populateObject) + public function testDeserializeResourceClassSupportedFormat(string $method, bool $populateObject): void + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['post' => ['input_formats' => self::FORMATS]] + )); + + $this->doTestDeserializeResourceClassSupportedFormat($method, $populateObject, $resourceMetadataFactoryProphecy->reveal()); + } + + /** + * @dataProvider methodProvider + */ + public function testLegacyDeserializeResourceClassSupportedFormat(string $method, bool $populateObject): void + { + $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $formatsProviderProphecy->getFormatsFromAttributes([ + 'resource_class' => 'Foo', + 'collection_operation_name' => 'post', + 'receive' => true, + 'respond' => true, + 'persist' => true, + ])->willReturn(self::FORMATS)->shouldBeCalled(); + + $this->doTestDeserializeResourceClassSupportedFormat($method, $populateObject, $formatsProviderProphecy->reveal()); + } + + private function doTestDeserializeResourceClassSupportedFormat(string $method, bool $populateObject, $resourceMetadataFactory): void { $result = $populateObject ? new \stdClass() : null; $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -177,26 +228,40 @@ public function testDeserializeResourceClassSupportedFormat(string $method, bool $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo'], 'resource_class' => 'Foo'])->shouldBeCalled(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes([ - 'resource_class' => 'Foo', - 'collection_operation_name' => 'post', - 'receive' => true, - 'respond' => true, - 'persist' => true, - ])->willReturn(self::FORMATS)->shouldBeCalled(); - - $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal()); + $listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $resourceMetadataFactory); $listener->onKernelRequest($eventProphecy->reveal()); } - public function methodProvider() + public function methodProvider(): iterable + { + yield ['POST', false]; + yield ['PUT', true]; + } + + public function testContentNegotiation(): void + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['post' => ['input_formats' => ['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']]]] + )); + + $this->doTestContentNegotiation($resourceMetadataFactoryProphecy->reveal()); + } + + public function testLegacyContentNegotiation(): void { - return [['POST', false], ['PUT', true]]; + $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); + $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']])->shouldBeCalled(); + + $this->doTestContentNegotiation($formatsProviderProphecy->reveal()); } - public function testContentNegotiation() + private function doTestContentNegotiation($resourceMetadataFactory): void { $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -214,18 +279,15 @@ public function testContentNegotiation() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn($context)->shouldBeCalled(); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']])->shouldBeCalled(); - $listener = new DeserializeListener( $serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), - $formatsProviderProphecy->reveal() + $resourceMetadataFactory ); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testNotSupportedContentType() + public function testNotSupportedContentType(): void { $this->expectException(UnsupportedMediaTypeHttpException::class); $this->expectExceptionMessage('The content-type "application/rdf+xml" is not supported. Supported MIME types are "application/ld+json", "text/xml".'); @@ -244,18 +306,24 @@ public function testNotSupportedContentType() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo']]); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']])->shouldBeCalled(); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + ['post' => ['input_formats' => ['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']]]] + )); $listener = new DeserializeListener( $serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), - $formatsProviderProphecy->reveal() + $resourceMetadataFactoryProphecy->reveal() ); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testNoContentType() + public function testNoContentType(): void { $this->expectException(UnsupportedMediaTypeHttpException::class); $this->expectExceptionMessage('The "Content-Type" header must exist.'); @@ -273,38 +341,22 @@ public function testNoContentType() $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn(['input' => ['class' => 'Foo'], 'output' => ['class' => 'Foo']]); - $formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class); - $formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->willReturn(['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']])->shouldBeCalled(); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, null, null, ['formats' => ['jsonld' => ['application/ld+json'], 'xml' => ['text/xml']]])); $listener = new DeserializeListener( $serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), - $formatsProviderProphecy->reveal() + $resourceMetadataFactoryProphecy->reveal() ); $listener->onKernelRequest($eventProphecy->reveal()); } - public function testBadFormatsProviderParameterThrowsException() - { - $this->expectException(\ApiPlatform\Core\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$formatsProvider" argument is expected to be an implementation of the "ApiPlatform\\Core\\Api\\FormatsProviderInterface" interface.'); - - $serializerProphecy = $this->prophesize(SerializerInterface::class); - - $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); - - new DeserializeListener( - $serializerProphecy->reveal(), - $serializerContextBuilderProphecy->reveal(), - 'foo' - ); - } - /** * @group legacy - * @expectedDeprecation Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3 + * @expectedDeprecation Passing an array or an instance of "ApiPlatform\Core\Api\FormatsProviderInterface" as 3rd parameter of the constructor of "ApiPlatform\Core\EventListener\DeserializeListener" is deprecated since API Platform 2.5, pass an instance of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface" instead */ - public function testLegacyFormatsParameter() + public function testLegacyFormatsParameter(): void { $serializerProphecy = $this->prophesize(SerializerInterface::class); diff --git a/tests/Fixtures/TestBundle/Document/CustomActionDummy.php b/tests/Fixtures/TestBundle/Document/CustomActionDummy.php index cc3c334e245..13b684adf2f 100644 --- a/tests/Fixtures/TestBundle/Document/CustomActionDummy.php +++ b/tests/Fixtures/TestBundle/Document/CustomActionDummy.php @@ -21,14 +21,14 @@ * @ApiResource(itemOperations={ * "get", * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies/{id}"}, - * "custom_normalization"={"route_name"="custom_normalization"}, - * "short_custom_normalization", + * "custom_normalization"={"method"="GET", "route_name"="custom_normalization"}, + * "short_custom_normalization"={"method"="GET", "route_name"="short_custom_normalization"}, * }, * collectionOperations={ * "get", * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies"}, - * "custom_denormalization"={"route_name"="custom_denormalization"}, - * "short_custom_denormalization", + * "custom_denormalization"={"method"="GET", "route_name"="custom_denormalization"}, + * "short_custom_denormalization"={"method"="GET", "route_name"="short_custom_denormalization"}, * }) * * @author Kévin Dunglas diff --git a/tests/Fixtures/TestBundle/Document/DummyValidation.php b/tests/Fixtures/TestBundle/Document/DummyValidation.php index c99ce45241b..f7d7ad895c8 100644 --- a/tests/Fixtures/TestBundle/Document/DummyValidation.php +++ b/tests/Fixtures/TestBundle/Document/DummyValidation.php @@ -23,8 +23,8 @@ * collectionOperations={ * "get"={"method"="GET"}, * "post"={"path"="dummy_validation.{_format}", "method"="POST"}, - * "post_validation_groups"={"route_name"="post_validation_groups", "validation_groups"={"a"}}, - * "post_validation_sequence"={"route_name"="post_validation_sequence", "validation_groups"="app.dummy_validation.group_generator"} + * "post_validation_groups"={"route_name"="post_validation_groups", "validation_groups"={"a"}, "method"="GET"}, + * "post_validation_sequence"={"route_name"="post_validation_sequence", "validation_groups"="app.dummy_validation.group_generator", "method"="GET"} * } * ) */ diff --git a/tests/Fixtures/TestBundle/Document/RelationEmbedder.php b/tests/Fixtures/TestBundle/Document/RelationEmbedder.php index 9333542db39..c2f0e0c798e 100644 --- a/tests/Fixtures/TestBundle/Document/RelationEmbedder.php +++ b/tests/Fixtures/TestBundle/Document/RelationEmbedder.php @@ -32,7 +32,7 @@ * "get", * "put"={}, * "delete", - * "custom_get"={"route_name"="relation_embedded.custom_get"}, + * "custom_get"={"route_name"="relation_embedded.custom_get", "method"="GET"}, * "custom1"={"path"="/api/custom-call/{id}", "method"="GET"}, * "custom2"={"path"="/api/custom-call/{id}", "method"="PUT"}, * } diff --git a/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php b/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php index 50755c58a51..1ceb19c85dc 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php +++ b/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php @@ -21,14 +21,14 @@ * @ApiResource(itemOperations={ * "get", * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies/{id}"}, - * "custom_normalization"={"route_name"="custom_normalization"}, - * "short_custom_normalization", + * "custom_normalization"={"route_name"="custom_normalization", "method"="GET"}, + * "short_custom_normalization"={"route_name"="short_custom_normalization", "method"="GET"}, * }, * collectionOperations={ * "get", * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies"}, - * "custom_denormalization"={"route_name"="custom_denormalization"}, - * "short_custom_denormalization", + * "custom_denormalization"={"route_name"="custom_denormalization", "method"="GET"}, + * "short_custom_denormalization"={"route_name"="short_custom_denormalization", "method"="GET"}, * }) * * @author Kévin Dunglas diff --git a/tests/Fixtures/TestBundle/Entity/DummyValidation.php b/tests/Fixtures/TestBundle/Entity/DummyValidation.php index c88a859f16f..214ae7e83df 100644 --- a/tests/Fixtures/TestBundle/Entity/DummyValidation.php +++ b/tests/Fixtures/TestBundle/Entity/DummyValidation.php @@ -23,8 +23,8 @@ * collectionOperations={ * "get"={"method"="GET"}, * "post"={"path"="dummy_validation.{_format}", "method"="POST"}, - * "post_validation_groups"={"route_name"="post_validation_groups", "validation_groups"={"a"}}, - * "post_validation_sequence"={"route_name"="post_validation_sequence", "validation_groups"="app.dummy_validation.group_generator"} + * "post_validation_groups"={"route_name"="post_validation_groups", "validation_groups"={"a"}, "method"="GET"}, + * "post_validation_sequence"={"route_name"="post_validation_sequence", "validation_groups"="app.dummy_validation.group_generator", "method"="GET"} * } * ) */ diff --git a/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php b/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php index 3ed034000c1..a10800e98ce 100644 --- a/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php +++ b/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php @@ -32,7 +32,7 @@ * "get", * "put"={}, * "delete", - * "custom_get"={"route_name"="relation_embedded.custom_get"}, + * "custom_get"={"route_name"="relation_embedded.custom_get", "method"="GET"}, * "custom1"={"path"="/api/custom-call/{id}", "method"="GET"}, * "custom2"={"path"="/api/custom-call/{id}", "method"="PUT"}, * } diff --git a/tests/Hydra/Serializer/DocumentationNormalizerTest.php b/tests/Hydra/Serializer/DocumentationNormalizerTest.php index a69d4c84080..0e33658587d 100644 --- a/tests/Hydra/Serializer/DocumentationNormalizerTest.php +++ b/tests/Hydra/Serializer/DocumentationNormalizerTest.php @@ -37,12 +37,28 @@ */ class DocumentationNormalizerTest extends TestCase { - public function testNormalize() + public function testNormalize(): void + { + $this->doTestNormalize(); + } + + public function testLegacyNormalize(): void + { + $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); + $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'put')->shouldBeCalled()->willReturn('PUT'); + $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'post')->shouldBeCalled()->willReturn('POST'); + + $this->doTestNormalize($operationMethodResolverProphecy->reveal()); + } + + private function doTestNormalize(OperationMethodResolverInterface $operationMethodResolver = null): void { $title = 'Test Api'; $desc = 'test ApiGerard'; $version = '0.0.0'; - $documentation = new Documentation(new ResourceNameCollection(['dummy' => 'dummy']), $title, $desc, $version, []); + $documentation = new Documentation(new ResourceNameCollection(['dummy' => 'dummy']), $title, $desc, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create('dummy', [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'description', 'nameConverted', 'relatedDummy'])); @@ -62,12 +78,6 @@ public function testNormalize() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'post')->shouldBeCalled()->willReturn('POST'); - $urlGenerator = $this->prophesize(UrlGeneratorInterface::class); $urlGenerator->generate('api_entrypoint')->willReturn('/')->shouldBeCalled(1); $urlGenerator->generate('api_doc', ['_format' => 'jsonld'])->willReturn('/doc')->shouldBeCalled(1); @@ -94,7 +104,7 @@ public function testNormalize() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + $operationMethodResolver, $urlGenerator->reveal(), $subresourceOperationFactoryProphecy->reveal(), new CustomConverter() @@ -358,13 +368,28 @@ public function testNormalizeInputOutputClass() $title = 'Test Api'; $desc = 'test ApiGerard'; $version = '0.0.0'; - $documentation = new Documentation(new ResourceNameCollection(['dummy' => 'dummy']), $title, $desc, $version, []); + $documentation = new Documentation(new ResourceNameCollection(['dummy' => 'dummy']), $title, $desc, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create('inputClass', [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['a', 'b'])); $propertyNameCollectionFactoryProphecy->create('outputClass', [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['c', 'd'])); - $dummyMetadata = new ResourceMetadata('dummy', 'dummy', '#dummy', ['get' => [], 'put' => ['input' => ['class' => null]]], ['get' => [], 'post' => ['output' => ['class' => null]]], ['input' => ['class' => 'inputClass'], 'output' => ['class' => 'outputClass']]); + $dummyMetadata = new ResourceMetadata( + 'dummy', + 'dummy', + '#dummy', + [ + 'get' => ['method' => 'GET'], + 'put' => ['method' => 'PUT', 'input' => ['class' => null]], + ], + [ + 'get' => ['method' => 'GET'], + 'post' => ['method' => 'POST', 'output' => ['class' => null]], + ], + [ + 'input' => ['class' => 'inputClass'], + 'output' => ['class' => 'outputClass'], + ]); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create('dummy')->shouldBeCalled()->willReturn($dummyMetadata); @@ -377,12 +402,6 @@ public function testNormalizeInputOutputClass() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'post')->shouldBeCalled()->willReturn('POST'); - $urlGenerator = $this->prophesize(UrlGeneratorInterface::class); $urlGenerator->generate('api_entrypoint')->willReturn('/')->shouldBeCalled(1); $urlGenerator->generate('api_doc', ['_format' => 'jsonld'])->willReturn('/doc')->shouldBeCalled(1); @@ -393,7 +412,7 @@ public function testNormalizeInputOutputClass() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $urlGenerator->reveal() ); diff --git a/tests/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php new file mode 100644 index 00000000000..8fb6e80b48f --- /dev/null +++ b/tests/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Tests\Metadata\Resource\Factory; + +use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Resource\Factory\FormatsResourceMetadataFactory; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use PHPUnit\Framework\TestCase; + +class FormatsResourceMetadataFactoryTest extends TestCase +{ + /** + * @dataProvider createProvider + */ + public function testCreate(ResourceMetadata $previous, ResourceMetadata $expected, array $formats = [], array $patchFormats = []): void + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn($previous); + + $actual = (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), $formats, $patchFormats))->create('Foo'); + $this->assertEquals($expected, $actual); + } + + public function createProvider(): iterable + { + yield [ + new ResourceMetadata( + null, + null, + null, + ['get' => []], + ['get' => []], + ['formats' => 'json'], + ['get' => []] + ), + new ResourceMetadata( + null, + null, + null, + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], + ['formats' => 'json'], + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] + ), + ['json' => ['application/json']], + ]; + + yield [ + new ResourceMetadata( + null, + null, + null, + ['get' => []], + ['get' => []], + ['formats' => ['json' => ['application/json']]], + ['get' => []] + ), + new ResourceMetadata( + null, + null, + null, + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], + ['formats' => ['json' => ['application/json']]], + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] + ), + ]; + + yield [ + new ResourceMetadata( + null, + null, + null, + ['get' => []], + ['get' => []], + ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => 'text/csv']], + ['get' => []] + ), + new ResourceMetadata( + null, + null, + null, + ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]], + ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]], + ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => 'text/csv']], + ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]] + ), + ]; + + yield [ + new ResourceMetadata( + null, + null, + null, + ['patch' => ['method' => 'PATCH']] + ), + new ResourceMetadata( + null, + null, + null, + ['patch' => ['method' => 'PATCH', 'input_formats' => ['json' => ['application/merge-patch+json']], 'output_formats' => []]] + ), + [], + ['json' => ['application/merge-patch+json']], + ]; + + yield [ + new ResourceMetadata( + null, + null, + null, + ['get' => ['formats' => 'json']] + ), + new ResourceMetadata( + null, + null, + null, + ['get' => ['formats' => ['json' => ['application/json']], 'input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] + ), + ['json' => ['application/json']], + ]; + + yield [ + new ResourceMetadata( + null, + null, + null, + ['get' => ['input_formats' => 'json', 'output_formats' => 'json']] + ), + new ResourceMetadata( + null, + null, + null, + ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] + ), + ['json' => ['application/json']], + ]; + } + + public function testInvalidFormatType(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The \'formats\' attributes value must be a string when trying to include an already configured format, object given.'); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + null, + ['formats' => [new \stdClass()]] + )); + + (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), [], []))->create('Foo'); + } + + public function testNotConfiguredFormat(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You either need to add the format \'xml\' to your project configuration or declare a mime type for it in your annotation.'); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( + null, + null, + null, + null, + null, + ['formats' => ['xml']] + )); + + (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), [], []))->create('Foo'); + } +} diff --git a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php index 48f7b6ed7ec..b990595792d 100644 --- a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php +++ b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php @@ -27,7 +27,7 @@ class OperationResourceMetadataFactoryTest extends TestCase /** * @dataProvider getMetadata */ - public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $after, array $formats = []) + public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $after, array $formats = []): void { $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $decoratedProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($before); @@ -35,29 +35,37 @@ public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $ $this->assertEquals($after, (new OperationResourceMetadataFactory($decoratedProphecy->reveal(), $formats))->create(Dummy::class)); } - public function getMetadata() + public function getMetadata(): iterable { $jsonapi = ['jsonapi' => ['application/vnd.api+json']]; - return [ - // Item operations - [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT'], 'delete' => ['method' => 'DELETE']], [], null, [], [])], - [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT'], 'patch' => ['method' => 'PATCH'], 'delete' => ['method' => 'DELETE']], [], null, [], []), $jsonapi], - [new ResourceMetadata(null, null, null, ['get'], [], null, [], []), new ResourceMetadata(null, null, null, ['get' => ['method' => 'GET']], [], null, [], [])], - [new ResourceMetadata(null, null, null, ['put'], [], null, [], []), new ResourceMetadata(null, null, null, ['put' => ['method' => 'PUT']], [], null, [], [])], - [new ResourceMetadata(null, null, null, ['delete'], [], null, [], []), new ResourceMetadata(null, null, null, ['delete' => ['method' => 'DELETE']], [], null, [], [])], - [new ResourceMetadata(null, null, null, ['patch'], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['route_name' => 'patch']], [], null, [], [])], - [new ResourceMetadata(null, null, null, ['patch'], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH']], [], null, [], []), $jsonapi], - [new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), $jsonapi], - [new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), $jsonapi], - - // Collection operations - [new ResourceMetadata(null, null, null, [], null, null, [], []), new ResourceMetadata(null, null, null, [], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['get'], null, [], []), new ResourceMetadata(null, null, null, [], ['get' => ['method' => 'GET']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['post'], null, [], []), new ResourceMetadata(null, null, null, [], ['post' => ['method' => 'POST']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['options'], null, [], []), new ResourceMetadata(null, null, null, [], ['options' => ['route_name' => 'options']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], [])], - ]; + // Item operations + yield [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get', 'put', 'delete']), [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get', 'put', 'patch', 'delete']), [], null, [], []), $jsonapi]; + yield [new ResourceMetadata(null, null, null, ['get'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get']), [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, ['put'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['put']), [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, ['delete'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['delete']), [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), $jsonapi]; + yield [new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), $jsonapi]; + yield [new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), $jsonapi]; + + // Collection operations + yield [new ResourceMetadata(null, null, null, [], null, null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['get', 'post']), null, [], [])]; + yield [new ResourceMetadata(null, null, null, [], ['get'], null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['get']), null, [], [])]; + yield [new ResourceMetadata(null, null, null, [], ['post'], null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['post']), null, [], [])]; + yield [new ResourceMetadata(null, null, null, [], ['options' => ['method' => 'OPTIONS', 'route_name' => 'options']], null, [], []), new ResourceMetadata(null, null, null, [], ['options' => ['route_name' => 'options', 'method' => 'OPTIONS']], null, [], [])]; + yield [new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], [])]; + yield [new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], [])]; + } + + private function getOperations(array $names): array + { + $operations = []; + foreach ($names as $name) { + $operations[$name] = ['method' => strtoupper($name)]; + } + + return $operations; } } diff --git a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php index 55038c58f21..007fd481aea 100644 --- a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php +++ b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php @@ -60,11 +60,16 @@ */ class DocumentationNormalizerV2Test extends TestCase { + private const OPERATION_FORMATS = [ + 'input_formats' => ['jsonld' => ['application/ld+json']], + 'output_formats' => ['jsonld' => ['application/ld+json']], + ]; + /** * @group legacy * @expectedDeprecation Passing an instance of ApiPlatform\Core\Api\UrlGeneratorInterface to ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer::__construct() is deprecated since version 2.1 and will be removed in 3.0. */ - public function testLegacyConstruct() + public function testLegacyConstruct(): void { $normalizer = new DocumentationNormalizer( $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), @@ -79,14 +84,47 @@ public function testLegacyConstruct() $this->assertInstanceOf(DocumentationNormalizer::class, $normalizer); } - public function testNormalize() + public function testNormalize(): void + { + $this->doTestNormalize(); + } + + public function testLegacyNormalize(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); + $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->shouldBeCalled()->willReturn('POST'); + + $this->doTestNormalize($operationMethodResolverProphecy->reveal()); + } + + private function doTestNormalize(OperationMethodResolverInterface $operationMethodResolver = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET', 'status' => '202'], 'put' => ['method' => 'PUT', 'status' => '202']], ['get' => ['method' => 'GET', 'status' => '202'], 'post' => ['method' => 'POST', 'status' => '202'], 'custom' => ['method' => 'GET', 'path' => '/foo', 'status' => '202'], 'custom2' => ['method' => 'POST', 'path' => '/foo']], ['pagination_client_items_per_page' => true]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'status' => '202'] + self::OPERATION_FORMATS, + ], + [ + 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST', 'status' => '202'] + self::OPERATION_FORMATS, + 'custom' => ['method' => 'GET', 'path' => '/foo', 'status' => '202'] + self::OPERATION_FORMATS, + 'custom2' => ['method' => 'POST', 'path' => '/foo'] + self::OPERATION_FORMATS, + ], + ['pagination_client_items_per_page' => true] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -97,14 +135,6 @@ public function testNormalize() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -112,7 +142,7 @@ public function testNormalize() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + $operationMethodResolver, $operationPathResolver ); @@ -317,14 +347,14 @@ public function testNormalize() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithNameConverter() + public function testNormalizeWithNameConverter(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET']], [], []); + $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS]); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -336,9 +366,6 @@ public function testNormalizeWithNameConverter() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $nameConverterProphecy = $this->prophesize( interface_exists(AdvancedNameConverterInterface::class) ? AdvancedNameConverterInterface::class @@ -354,7 +381,7 @@ interface_exists(AdvancedNameConverterInterface::class) $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -432,14 +459,14 @@ interface_exists(AdvancedNameConverterInterface::class) $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithApiKeysEnabled() + public function testNormalizeWithApiKeysEnabled(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET']], [], []); + $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS]); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -450,9 +477,6 @@ public function testNormalizeWithApiKeysEnabled() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $apiKeysConfiguration = [ @@ -471,7 +495,7 @@ public function testNormalizeWithApiKeysEnabled() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -553,13 +577,12 @@ public function testNormalizeWithApiKeysEnabled() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithOnlyNormalizationGroups() + public function testNormalizeWithOnlyNormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $groups = ['dummy', 'foo', 'bar']; $ref = 'Dummy-'.implode('_', $groups); @@ -573,14 +596,13 @@ public function testNormalizeWithOnlyNormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -592,12 +614,6 @@ public function testNormalizeWithOnlyNormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -605,7 +621,7 @@ public function testNormalizeWithOnlyNormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -747,9 +763,9 @@ public function testNormalizeWithOnlyNormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithSwaggerDefinitionName() + public function testNormalizeWithSwaggerDefinitionName(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id'])); @@ -764,7 +780,7 @@ public function testNormalizeWithSwaggerDefinitionName() 'normalization_context' => [ DocumentationNormalizer::SWAGGER_DEFINITION_NAME => 'Read', ], - ], + ] + self::OPERATION_FORMATS, ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); @@ -775,9 +791,6 @@ public function testNormalizeWithSwaggerDefinitionName() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -785,7 +798,7 @@ public function testNormalizeWithSwaggerDefinitionName() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -841,13 +854,12 @@ public function testNormalizeWithSwaggerDefinitionName() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithOnlyDenormalizationGroups() + public function testNormalizeWithOnlyDenormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, ['serializer_groups' => 'dummy'])->shouldBeCalled(1)->willReturn(new PropertyNameCollection(['gerard'])); @@ -858,14 +870,13 @@ public function testNormalizeWithOnlyDenormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -877,12 +888,6 @@ public function testNormalizeWithOnlyDenormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -890,7 +895,7 @@ public function testNormalizeWithOnlyDenormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1032,13 +1037,12 @@ public function testNormalizeWithOnlyDenormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithNormalizationAndDenormalizationGroups() + public function testNormalizeWithNormalizationAndDenormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, ['serializer_groups' => 'dummy'])->shouldBeCalled(1)->willReturn(new PropertyNameCollection(['gerard'])); @@ -1049,17 +1053,16 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, 'put' => [ 'method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], - ], + ] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1071,12 +1074,6 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1084,7 +1081,7 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1226,14 +1223,27 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeSkipsNotReadableAndNotWritableProperties() + public function testNormalizeSkipsNotReadableAndNotWritableProperties(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'dummy', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET', 'status' => '202'], 'put' => ['method' => 'PUT', 'status' => '202']], ['get' => ['method' => 'GET', 'status' => '202'], 'post' => ['method' => 'POST', 'status' => '202']], ['pagination_client_items_per_page' => true]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'status' => '202'] + self::OPERATION_FORMATS, + ], + [ + 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST', 'status' => '202'] + self::OPERATION_FORMATS, + ], + ['pagination_client_items_per_page' => true] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1244,12 +1254,6 @@ public function testNormalizeSkipsNotReadableAndNotWritableProperties() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1257,7 +1261,7 @@ public function testNormalizeSkipsNotReadableAndNotWritableProperties() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1402,7 +1406,7 @@ public function testNormalizeSkipsNotReadableAndNotWritableProperties() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testFilters() + public function testFilters(): void { $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); $filters = [ @@ -1442,7 +1446,7 @@ public function testFilters() * @group legacy * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. */ - public function testFiltersWithDeprecatedFilterCollection() + public function testFiltersWithDeprecatedFilterCollection(): void { $this->normalizeWithFilters(new FilterCollection([ 'f1' => new DummyFilter(['name' => [ @@ -1468,7 +1472,7 @@ public function testFiltersWithDeprecatedFilterCollection() ])); } - public function testConstructWithInvalidFilterLocator() + public function testConstructWithInvalidFilterLocator(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface or null.'); @@ -1478,20 +1482,19 @@ public function testConstructWithInvalidFilterLocator() $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), $this->prophesize(ResourceClassResolverInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal(), + null, $this->prophesize(OperationPathResolverInterface::class)->reveal(), null, new \ArrayObject() ); } - public function testSupports() + public function testSupports(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1499,11 +1502,11 @@ public function testSupports() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $this->assertTrue($normalizer->supportsNormalization($documentation, 'json')); $this->assertFalse($normalizer->supportsNormalization($documentation)); @@ -1511,20 +1514,16 @@ public function testSupports() $this->assertTrue($normalizer->hasCacheableSupportsMethod()); } - public function testNoOperations() + public function testNoOperations(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldNotBeCalled(); $dummyMetadata = new ResourceMetadata( 'Dummy', - 'This is a dummy.', - null, - [], - [], - [] + 'This is a dummy.' ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1535,9 +1534,6 @@ public function testNoOperations() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldNotBeCalled(); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1545,7 +1541,7 @@ public function testNoOperations() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1562,9 +1558,9 @@ public function testNoOperations() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testWithCustomMethod() + public function testWithCustomMethod(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); @@ -1573,8 +1569,7 @@ public function testWithCustomMethod() 'This is a dummy.', null, [], - ['get' => ['method' => 'FOO']], - [] + ['get' => ['method' => 'FOO']] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1584,9 +1579,6 @@ public function testWithCustomMethod() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('FOO'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1594,7 +1586,7 @@ public function testWithCustomMethod() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1618,13 +1610,12 @@ public function testWithCustomMethod() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithNestedNormalizationGroups() + public function testNormalizeWithNestedNormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $groups = ['dummy', 'foo', 'bar']; $ref = 'Dummy-'.implode('_', $groups); $relatedDummyRef = 'RelatedDummy-'.implode('_', $groups); @@ -1639,14 +1630,13 @@ public function testNormalizeWithNestedNormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $relatedDummyMetadata = new ResourceMetadata( @@ -1654,10 +1644,8 @@ public function testNormalizeWithNestedNormalizationGroups() 'This is a related dummy.', 'http://schema.example.com/RelatedDummy', [ - 'get' => ['method' => 'GET'], - ], - [], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); @@ -1673,12 +1661,6 @@ public function testNormalizeWithNestedNormalizationGroups() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1686,7 +1668,7 @@ public function testNormalizeWithNestedNormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -1843,9 +1825,9 @@ public function testNormalizeWithNestedNormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - private function normalizeWithFilters($filterLocator) + private function normalizeWithFilters($filterLocator): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); @@ -1855,8 +1837,7 @@ private function normalizeWithFilters($filterLocator) 'This is a dummy.', null, [], - ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4']]], - [] + ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4']] + self::OPERATION_FORMATS] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1867,9 +1848,6 @@ private function normalizeWithFilters($filterLocator) $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1877,7 +1855,7 @@ private function normalizeWithFilters($filterLocator) $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, $filterLocator @@ -1958,16 +1936,43 @@ private function normalizeWithFilters($filterLocator) $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithSubResource() + public function testNormalizeWithSubResource(): void { - $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $this->doTestNormalizeWithSubResource(); + } + + public function testLegacyNormalizeWithSubResource(): void + { + $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); + $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); + $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); + + $this->doTestNormalizeWithSubResource($formatProviderProphecy->reveal()); + } + + private function doTestNormalizeWithSubResource(OperationAwareFormatsProviderInterface $formatsProvider = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Question::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['answer'])); $propertyNameCollectionFactoryProphecy->create(Answer::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['content'])); - $questionMetadata = new ResourceMetadata('Question', 'This is a question.', 'http://schema.example.com/Question', ['get' => ['method' => 'GET']]); - $answerMetadata = new ResourceMetadata('Answer', 'This is an answer.', 'http://schema.example.com/Answer', [], ['get' => ['method' => 'GET']]); + $questionMetadata = new ResourceMetadata( + 'Question', + 'This is a question.', + 'http://schema.example.com/Question', + ['get' => ['method' => 'GET', 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]]] + ); + $answerMetadata = new ResourceMetadata( + 'Answer', + 'This is an answer.', + 'http://schema.example.com/Answer', + [], + ['get' => ['method' => 'GET']] + self::OPERATION_FORMATS, + [], + ['get' => ['method' => 'GET', 'input_formats' => ['xml' => ['text/xml']], 'output_formats' => ['xml' => ['text/xml']]]] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Question::class)->shouldBeCalled()->willReturn($questionMetadata); $resourceMetadataFactoryProphecy->create(Answer::class)->shouldBeCalled()->willReturn($answerMetadata); @@ -1981,9 +1986,6 @@ public function testNormalizeWithSubResource() $resourceClassResolverProphecy->isResourceClass(Question::class)->willReturn(true); $resourceClassResolverProphecy->isResourceClass(Answer::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Question::class, 'get')->shouldBeCalled()->willReturn('GET'); - $routeCollection = new RouteCollection(); $routeCollection->add('api_questions_answer_get_subresource', new Route('/api/questions/{id}/answer.{_format}')); $routeCollection->add('api_questions_get_item', new Route('/api/questions/{id}.{_format}')); @@ -1997,10 +1999,6 @@ public function testNormalizeWithSubResource() $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new UnderscorePathSegmentNameGenerator()); $normalizer = new DocumentationNormalizer( @@ -2008,7 +2006,7 @@ public function testNormalizeWithSubResource() $propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2025,7 +2023,7 @@ public function testNormalizeWithSubResource() 'page', false, 'itemsPerPage', - $formatProviderProphecy->reveal() + $formatsProvider ?? ['json' => ['application/json'], 'csv' => ['text/csv']] ); $expected = [ @@ -2115,14 +2113,19 @@ public function testNormalizeWithSubResource() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithPropertySwaggerContext() + public function testNormalizeWithPropertySwaggerContext(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET']]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2132,9 +2135,6 @@ public function testNormalizeWithPropertySwaggerContext() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2142,7 +2142,7 @@ public function testNormalizeWithPropertySwaggerContext() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -2204,14 +2204,20 @@ public function testNormalizeWithPropertySwaggerContext() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithPaginationClientEnabled() + public function testNormalizeWithPaginationClientEnabled(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', [], ['get' => ['method' => 'GET', 'pagination_client_enabled' => true]]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [], + ['get' => ['method' => 'GET', 'pagination_client_enabled' => true] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2221,9 +2227,6 @@ public function testNormalizeWithPaginationClientEnabled() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2231,7 +2234,7 @@ public function testNormalizeWithPaginationClientEnabled() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); @@ -2303,14 +2306,40 @@ public function testNormalizeWithPaginationClientEnabled() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() + public function testNormalizeWithCustomFormatsDefinedAtOperationLevel(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(); + } + + public function testLegacyNormalizeWithCustomFormatsDefinedAtOperationLevel(): void + { + $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); + + $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel($formatProviderProphecy->reveal()); + } + + private function doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(OperationAwareFormatsProviderInterface $formatProvider = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET', 'output_formats' => ['jsonapi' => ['application/vnd.api+json']]], + 'put' => ['method' => 'PUT', 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]], ], + [ + 'get' => ['method' => 'GET', 'output_formats' => ['xml' => ['application/xml', 'text/xml']]], + 'post' => ['method' => 'POST', 'output_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']], 'input_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']]], + ]); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2320,26 +2349,14 @@ public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new OperationPathResolver(new UnderscorePathSegmentNameGenerator()); - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $normalizer = new DocumentationNormalizer( $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2350,13 +2367,12 @@ public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() '', [], [], - null, false, 'page', false, 'itemsPerPage', - $formatProviderProphecy->reveal() + $formatProvider ); $expected = [ @@ -2485,16 +2501,37 @@ public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/'])); } - public function testNormalizeWithInputAndOutputClass() + public function testLegacyNormalizeWithInputAndOutputClass(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $this->doTestNormalizeWithInputAndOutputClass(); + } + + private function doTestNormalizeWithInputAndOutputClass(): void + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); //$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description'])); $propertyNameCollectionFactoryProphecy->create(InputDto::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['foo', 'bar'])); $propertyNameCollectionFactoryProphecy->create(OutputDto::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['baz', 'bat'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => [], 'put' => []], ['get' => [], 'post' => []], ['input' => ['class' => InputDto::class], 'output' => ['class' => OutputDto::class]]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, + ], + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ], + [ + 'input' => ['class' => InputDto::class], + 'output' => ['class' => OutputDto::class], + ] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2512,12 +2549,6 @@ public function testNormalizeWithInputAndOutputClass() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2525,7 +2556,7 @@ public function testNormalizeWithInputAndOutputClass() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver ); diff --git a/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php b/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php index ab3fbc65ed0..d7ab92d2d52 100644 --- a/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php +++ b/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php @@ -57,15 +57,53 @@ */ class DocumentationNormalizerV3Test extends TestCase { - public function testNormalize() + private const OPERATION_FORMATS = [ + 'input_formats' => ['jsonld' => ['application/ld+json']], + 'output_formats' => ['jsonld' => ['application/ld+json']], + ]; + + public function testNormalize(): void + { + $this->doTestNormalize(); + } + + public function testLegacyNormalize(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); + $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->shouldBeCalled()->willReturn('GET'); + $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->shouldBeCalled()->willReturn('POST'); + + $this->doTestNormalize($operationMethodResolverProphecy->reveal()); + } + + private function doTestNormalize(OperationMethodResolverInterface $operationMethodResolver = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description'])); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST'], 'custom' => ['method' => 'GET', 'path' => '/foo'], 'custom2' => ['method' => 'POST', 'path' => '/foo']], ['pagination_client_items_per_page' => true]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, + ], + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + 'custom' => ['method' => 'GET', 'path' => '/foo'] + self::OPERATION_FORMATS, + 'custom2' => ['method' => 'POST', 'path' => '/foo'] + self::OPERATION_FORMATS, + ], + ['pagination_client_items_per_page' => true] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -76,14 +114,6 @@ public function testNormalize() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -91,7 +121,7 @@ public function testNormalize() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + $operationMethodResolver, $operationPathResolver, null, null, @@ -108,7 +138,7 @@ public function testNormalize() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -354,15 +384,20 @@ public function testNormalize() $this->assertArrayNotHasKey('servers', (array) $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => ''])); } - public function testNormalizeWithNameConverter() + public function testNormalizeWithNameConverter(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET']], [], []); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + null, + ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -374,9 +409,6 @@ public function testNormalizeWithNameConverter() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); $nameConverterProphecy->normalize('name', Dummy::class, DocumentationNormalizer::FORMAT, [])->willReturn('name')->shouldBeCalled(); $nameConverterProphecy->normalize('nameConverted', Dummy::class, DocumentationNormalizer::FORMAT, [])->willReturn('name_converted')->shouldBeCalled(); @@ -388,7 +420,7 @@ public function testNormalizeWithNameConverter() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -405,7 +437,7 @@ public function testNormalizeWithNameConverter() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -480,15 +512,20 @@ public function testNormalizeWithNameConverter() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithApiKeysEnabled() + public function testNormalizeWithApiKeysEnabled(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET']], [], []); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + null, + ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -499,9 +536,6 @@ public function testNormalizeWithApiKeysEnabled() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $apiKeysConfiguration = [ @@ -520,7 +554,7 @@ public function testNormalizeWithApiKeysEnabled() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -537,7 +571,7 @@ public function testNormalizeWithApiKeysEnabled() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -616,13 +650,12 @@ public function testNormalizeWithApiKeysEnabled() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithOnlyNormalizationGroups() + public function testNormalizeWithOnlyNormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $groups = ['dummy', 'foo', 'bar']; $ref = 'Dummy-'.implode('_', $groups); @@ -637,14 +670,13 @@ public function testNormalizeWithOnlyNormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -656,12 +688,6 @@ public function testNormalizeWithOnlyNormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -669,7 +695,7 @@ public function testNormalizeWithOnlyNormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -686,7 +712,7 @@ public function testNormalizeWithOnlyNormalizationGroups() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -847,9 +873,9 @@ public function testNormalizeWithOnlyNormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithOpenApiDefinitionName() + public function testNormalizeWithOpenApiDefinitionName(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id'])); @@ -865,7 +891,7 @@ public function testNormalizeWithOpenApiDefinitionName() 'normalization_context' => [ DocumentationNormalizer::SWAGGER_DEFINITION_NAME => 'Read', ], - ], + ] + self::OPERATION_FORMATS, ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); @@ -876,9 +902,6 @@ public function testNormalizeWithOpenApiDefinitionName() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -886,7 +909,7 @@ public function testNormalizeWithOpenApiDefinitionName() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -903,7 +926,7 @@ public function testNormalizeWithOpenApiDefinitionName() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -966,13 +989,12 @@ public function testNormalizeWithOpenApiDefinitionName() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithOnlyDenormalizationGroups() + public function testNormalizeWithOnlyDenormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, ['serializer_groups' => 'dummy'])->shouldBeCalled(1)->willReturn(new PropertyNameCollection(['gerard'])); @@ -984,14 +1006,13 @@ public function testNormalizeWithOnlyDenormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1003,12 +1024,6 @@ public function testNormalizeWithOnlyDenormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1016,7 +1031,7 @@ public function testNormalizeWithOnlyDenormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1033,7 +1048,7 @@ public function testNormalizeWithOnlyDenormalizationGroups() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -1192,13 +1207,12 @@ public function testNormalizeWithOnlyDenormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithNormalizationAndDenormalizationGroups() + public function testNormalizeWithNormalizationAndDenormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, ['serializer_groups' => 'dummy'])->shouldBeCalled(1)->willReturn(new PropertyNameCollection(['gerard'])); @@ -1210,17 +1224,16 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, 'put' => [ 'method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], - ], + ] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1232,12 +1245,6 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1245,7 +1252,7 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1262,7 +1269,7 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -1421,7 +1428,7 @@ public function testNormalizeWithNormalizationAndDenormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testFilters() + public function testFilters(): void { $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); $filters = [ @@ -1461,7 +1468,7 @@ public function testFilters() * @group legacy * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. */ - public function testFiltersWithDeprecatedFilterCollection() + public function testFiltersWithDeprecatedFilterCollection(): void { $this->normalizeWithFilters(new FilterCollection([ 'f1' => new DummyFilter(['name' => [ @@ -1487,7 +1494,7 @@ public function testFiltersWithDeprecatedFilterCollection() ])); } - public function testConstructWithInvalidFilterLocator() + public function testConstructWithInvalidFilterLocator(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface or null.'); @@ -1497,7 +1504,7 @@ public function testConstructWithInvalidFilterLocator() $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), $this->prophesize(ResourceClassResolverInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal(), + null, $this->prophesize(OperationPathResolverInterface::class)->reveal(), null, new \ArrayObject(), @@ -1514,20 +1521,19 @@ public function testConstructWithInvalidFilterLocator() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] ); } - public function testSupports() + public function testSupports(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1535,7 +1541,7 @@ public function testSupports() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1552,13 +1558,13 @@ public function testSupports() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] ); - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $this->assertTrue($normalizer->supportsNormalization($documentation, 'json')); $this->assertFalse($normalizer->supportsNormalization($documentation)); @@ -1566,9 +1572,9 @@ public function testSupports() $this->assertTrue($normalizer->hasCacheableSupportsMethod()); } - public function testNoOperations() + public function testNoOperations(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldNotBeCalled(); @@ -1579,7 +1585,7 @@ public function testNoOperations() null, [], [], - [] + ['formats' => ['jsonld' => ['application/ld+json']]] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1590,9 +1596,6 @@ public function testNoOperations() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldNotBeCalled(); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1600,7 +1603,7 @@ public function testNoOperations() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1617,7 +1620,7 @@ public function testNoOperations() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -1635,9 +1638,9 @@ public function testNoOperations() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testWithCustomMethod() + public function testWithCustomMethod(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); @@ -1646,8 +1649,7 @@ public function testWithCustomMethod() 'This is a dummy.', null, [], - ['get' => ['method' => 'FOO']], - [] + ['get' => ['method' => 'FOO'] + self::OPERATION_FORMATS] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1657,9 +1659,6 @@ public function testWithCustomMethod() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('FOO'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1667,7 +1666,7 @@ public function testWithCustomMethod() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1684,7 +1683,7 @@ public function testWithCustomMethod() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -1709,13 +1708,12 @@ public function testWithCustomMethod() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithNestedNormalizationGroups() + public function testNormalizeWithNestedNormalizationGroups(): void { $title = 'Test API'; $description = 'This is a test API.'; - $formats = ['jsonld' => ['application/ld+json']]; $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version, $formats); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); $groups = ['dummy', 'foo', 'bar']; $ref = 'Dummy-'.implode('_', $groups); $relatedDummyRef = 'RelatedDummy-'.implode('_', $groups); @@ -1731,14 +1729,13 @@ public function testNormalizeWithNestedNormalizationGroups() 'This is a dummy.', 'http://schema.example.com/Dummy', [ - 'get' => ['method' => 'GET'], - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]], + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, ], [ - 'get' => ['method' => 'GET'], - 'post' => ['method' => 'POST'], - ], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, + ] ); $relatedDummyMetadata = new ResourceMetadata( @@ -1746,10 +1743,8 @@ public function testNormalizeWithNestedNormalizationGroups() 'This is a related dummy.', 'http://schema.example.com/RelatedDummy', [ - 'get' => ['method' => 'GET'], - ], - [], - [] + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); @@ -1765,12 +1760,6 @@ public function testNormalizeWithNestedNormalizationGroups() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -1778,7 +1767,7 @@ public function testNormalizeWithNestedNormalizationGroups() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -1795,7 +1784,7 @@ public function testNormalizeWithNestedNormalizationGroups() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -1969,9 +1958,9 @@ public function testNormalizeWithNestedNormalizationGroups() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - private function normalizeWithFilters($filterLocator) + private function normalizeWithFilters($filterLocator): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); @@ -1981,8 +1970,7 @@ private function normalizeWithFilters($filterLocator) 'This is a dummy.', null, [], - ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4']]], - [] + ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4']] + self::OPERATION_FORMATS] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -1993,9 +1981,6 @@ private function normalizeWithFilters($filterLocator) $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2003,7 +1988,7 @@ private function normalizeWithFilters($filterLocator) $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, $filterLocator, @@ -2020,7 +2005,7 @@ private function normalizeWithFilters($filterLocator) 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -2108,16 +2093,43 @@ private function normalizeWithFilters($filterLocator) $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithSubResource() + public function testNormalizeWithSubResource(): void + { + $this->doTestNormalizeWithSubResource(); + } + + public function testLegacytNormalizeWithSubResource(): void { - $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); + $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); + $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); + + $this->doTestNormalizeWithSubResource($formatProviderProphecy->reveal()); + } + + private function doTestNormalizeWithSubResource(OperationAwareFormatsProviderInterface $formatProvider = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Question::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['answer'])); $propertyNameCollectionFactoryProphecy->create(Answer::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['content'])); - $questionMetadata = new ResourceMetadata('Question', 'This is a question.', 'http://schema.example.com/Question', ['get' => ['method' => 'GET']]); - $answerMetadata = new ResourceMetadata('Answer', 'This is an answer.', 'http://schema.example.com/Answer', [], ['get' => ['method' => 'GET']]); + $questionMetadata = new ResourceMetadata( + 'Question', + 'This is a question.', + 'http://schema.example.com/Question', + ['get' => ['method' => 'GET', 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]]] + ); + $answerMetadata = new ResourceMetadata( + 'Answer', + 'This is an answer.', + 'http://schema.example.com/Answer', + [], + ['get' => ['method' => 'GET']] + self::OPERATION_FORMATS, + [], + ['get' => ['method' => 'GET', 'input_formats' => ['xml' => ['text/xml']], 'output_formats' => ['xml' => ['text/xml']]]] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Question::class)->shouldBeCalled()->willReturn($questionMetadata); $resourceMetadataFactoryProphecy->create(Answer::class)->shouldBeCalled()->willReturn($answerMetadata); @@ -2131,9 +2143,6 @@ public function testNormalizeWithSubResource() $resourceClassResolverProphecy->isResourceClass(Question::class)->willReturn(true); $resourceClassResolverProphecy->isResourceClass(Answer::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Question::class, 'get')->shouldBeCalled()->willReturn('GET'); - $routeCollection = new RouteCollection(); $routeCollection->add('api_questions_answer_get_subresource', new Route('/api/questions/{id}/answer.{_format}')); $routeCollection->add('api_questions_get_item', new Route('/api/questions/{id}.{_format}')); @@ -2149,16 +2158,12 @@ public function testNormalizeWithSubResource() $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new UnderscorePathSegmentNameGenerator()); - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); - $normalizer = new DocumentationNormalizer( $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2175,7 +2180,7 @@ public function testNormalizeWithSubResource() 'page', false, 'itemsPerPage', - $formatProviderProphecy->reveal(), + $formatProvider ?? [], false, 'pagination', ['spec_version' => 3] @@ -2278,15 +2283,20 @@ public function testNormalizeWithSubResource() $this->assertEquals($expected, $normalizer->normalize($documentation)); } - public function testNormalizeWithPropertyOpenApiContext() + public function testNormalizeWithPropertyOpenApiContext(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET']]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2296,9 +2306,6 @@ public function testNormalizeWithPropertyOpenApiContext() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2306,7 +2313,7 @@ public function testNormalizeWithPropertyOpenApiContext() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2323,7 +2330,7 @@ public function testNormalizeWithPropertyOpenApiContext() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -2392,14 +2399,20 @@ public function testNormalizeWithPropertyOpenApiContext() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithPaginationClientEnabled() + public function testNormalizeWithPaginationClientEnabled(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', [], ['get' => ['method' => 'GET', 'pagination_client_enabled' => true]]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [], + ['get' => ['method' => 'GET', 'pagination_client_enabled' => true] + self::OPERATION_FORMATS] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2409,9 +2422,6 @@ public function testNormalizeWithPaginationClientEnabled() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); $normalizer = new DocumentationNormalizer( @@ -2419,7 +2429,7 @@ public function testNormalizeWithPaginationClientEnabled() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2436,7 +2446,7 @@ public function testNormalizeWithPaginationClientEnabled() 'page', false, 'itemsPerPage', - null, + [], false, 'pagination', ['spec_version' => 3] @@ -2515,15 +2525,42 @@ public function testNormalizeWithPaginationClientEnabled() $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); } - public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() + public function testNormalizeWithCustomFormatsDefinedAtOperationLevel(): void { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]); + $this->doNormalizeWithCustomFormatsDefinedAtOperationLevel(); + } + + public function testLegacyNormalizeWithCustomFormatsDefinedAtOperationLevel(): void + { + $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); + $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); + + $this->doNormalizeWithCustomFormatsDefinedAtOperationLevel($formatProviderProphecy->reveal()); + } + + private function doNormalizeWithCustomFormatsDefinedAtOperationLevel(OperationAwareFormatsProviderInterface $formatsProvider = null): void + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name'])); - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']]); + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET', 'output_formats' => ['jsonapi' => ['application/vnd.api+json']]], + 'put' => ['method' => 'PUT', 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]], ], + [ + 'get' => ['method' => 'GET', 'output_formats' => ['xml' => ['application/xml', 'text/xml']]], + 'post' => ['method' => 'POST', 'output_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']], 'input_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']]], + ] + ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); @@ -2533,26 +2570,14 @@ public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->shouldBeCalled()->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST'); - $operationPathResolver = new OperationPathResolver(new UnderscorePathSegmentNameGenerator()); - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $normalizer = new DocumentationNormalizer( $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $operationMethodResolverProphecy->reveal(), + null, $operationPathResolver, null, null, @@ -2563,13 +2588,12 @@ public function testNormalizeWithCustomFormatsDefinedAtOperationLevel() '', [], [], - null, false, 'page', false, 'itemsPerPage', - $formatProviderProphecy->reveal(), + $formatsProvider ?? [], false, 'pagination', ['spec_version' => 3]