From 7d431f567698c537de9606139dd916ed53c72c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 2 Jul 2016 16:27:20 +0200 Subject: [PATCH 01/10] Refactor the responder to ease usage of content negotiation --- ...-filter.feature => boolean_filter.feature} | 0 ...ate-filter.feature => date_filter.feature} | 0 ...-filter.feature => numeric_filter.feature} | 0 ...er-filter.feature => order_filter.feature} | 0 ...ge-filter.feature => range_filter.feature} | 0 ...h-filter.feature => search_filter.feature} | 0 .../{fos-user.feature => fos_user.feature} | 1 + features/{json-ld => jsonld}/context.feature | 0 ...e.feature => overriding_attribute.feature} | 0 .../Symfony/Bundle/Resources/config/api.xml | 12 ++- .../Bundle/Resources/config/doctrine_orm.xml | 2 +- .../Bundle/Resources/config/jsonld.xml | 8 -- .../ValidationExceptionListener.php | 7 +- src/EventListener/FormatRequestListener.php | 13 ++- src/EventListener/ResponderViewListener.php | 49 ++++++++++ src/EventListener/SerializerViewListener.php | 61 ++++++++++++ src/Hydra/Action/DocumentationAction.php | 11 +-- src/Hydra/Action/ExceptionAction.php | 22 ++--- src/JsonLd/Action/ContextAction.php | 14 +-- src/JsonLd/Action/EntrypointAction.php | 14 +-- .../EventListener/ResponderViewListener.php | 92 ------------------ src/JsonLd/Response.php | 36 ------- src/JsonLd/Serializer/JsonLdEncoder.php | 10 ++ .../ApiPlatformExtensionTest.php | 6 +- .../FormatRequestListenerTest.php | 5 + .../Controller/CustomController.php | 9 +- .../XmlResponderViewListener.php | 93 ------------------- tests/Fixtures/app/config/config.yml | 9 -- 28 files changed, 178 insertions(+), 296 deletions(-) rename features/doctrine/{boolean-filter.feature => boolean_filter.feature} (100%) rename features/doctrine/{date-filter.feature => date_filter.feature} (100%) rename features/doctrine/{numeric-filter.feature => numeric_filter.feature} (100%) rename features/doctrine/{order-filter.feature => order_filter.feature} (100%) rename features/doctrine/{range-filter.feature => range_filter.feature} (100%) rename features/doctrine/{search-filter.feature => search_filter.feature} (100%) rename features/{fos-user.feature => fos_user.feature} (96%) rename features/{json-ld => jsonld}/context.feature (100%) rename features/{overriding-attribute.feature => overriding_attribute.feature} (100%) create mode 100644 src/EventListener/ResponderViewListener.php create mode 100644 src/EventListener/SerializerViewListener.php delete mode 100644 src/JsonLd/EventListener/ResponderViewListener.php delete mode 100644 src/JsonLd/Response.php delete mode 100644 tests/Fixtures/TestBundle/EventListener/XmlResponderViewListener.php diff --git a/features/doctrine/boolean-filter.feature b/features/doctrine/boolean_filter.feature similarity index 100% rename from features/doctrine/boolean-filter.feature rename to features/doctrine/boolean_filter.feature diff --git a/features/doctrine/date-filter.feature b/features/doctrine/date_filter.feature similarity index 100% rename from features/doctrine/date-filter.feature rename to features/doctrine/date_filter.feature diff --git a/features/doctrine/numeric-filter.feature b/features/doctrine/numeric_filter.feature similarity index 100% rename from features/doctrine/numeric-filter.feature rename to features/doctrine/numeric_filter.feature diff --git a/features/doctrine/order-filter.feature b/features/doctrine/order_filter.feature similarity index 100% rename from features/doctrine/order-filter.feature rename to features/doctrine/order_filter.feature diff --git a/features/doctrine/range-filter.feature b/features/doctrine/range_filter.feature similarity index 100% rename from features/doctrine/range-filter.feature rename to features/doctrine/range_filter.feature diff --git a/features/doctrine/search-filter.feature b/features/doctrine/search_filter.feature similarity index 100% rename from features/doctrine/search-filter.feature rename to features/doctrine/search_filter.feature diff --git a/features/fos-user.feature b/features/fos_user.feature similarity index 96% rename from features/fos-user.feature rename to features/fos_user.feature index 7ef920fd8bd..9d8e186643c 100644 --- a/features/fos-user.feature +++ b/features/fos_user.feature @@ -10,6 +10,7 @@ Feature: Create-Retrieve-Update-Delete """ { "fullname": "Dummy User", + "username": "dummy.user", "email": "dummy.user@example.com", "plainPassword": "azerty" } diff --git a/features/json-ld/context.feature b/features/jsonld/context.feature similarity index 100% rename from features/json-ld/context.feature rename to features/jsonld/context.feature diff --git a/features/overriding-attribute.feature b/features/overriding_attribute.feature similarity index 100% rename from features/overriding-attribute.feature rename to features/overriding_attribute.feature diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 86cd5eddd42..9195faafccc 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -58,6 +58,12 @@ + + + + + + @@ -68,7 +74,11 @@ - + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml index 0a322a171c0..4656cfa07eb 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -76,7 +76,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml index 75783eff304..3a442234b58 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml @@ -39,14 +39,6 @@ - - - - - - - - diff --git a/src/Bridge/Symfony/Validator/Hydra/EventListener/ValidationExceptionListener.php b/src/Bridge/Symfony/Validator/Hydra/EventListener/ValidationExceptionListener.php index fcdda16cd8f..9a35ebabb21 100644 --- a/src/Bridge/Symfony/Validator/Hydra/EventListener/ValidationExceptionListener.php +++ b/src/Bridge/Symfony/Validator/Hydra/EventListener/ValidationExceptionListener.php @@ -12,7 +12,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Validator\Hydra\EventListener; use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; -use ApiPlatform\Core\JsonLd\Response; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -40,9 +40,10 @@ public function onKernelException(GetResponseForExceptionEvent $event) $exception = $event->getException(); if ($exception instanceof ValidationException) { - $event->setResponse(new Response( + $event->setResponse(new JsonResponse( $this->normalizer->normalize($exception->getConstraintViolationList(), 'hydra-error'), - Response::HTTP_BAD_REQUEST + JsonResponse::HTTP_BAD_REQUEST, + ['Content-Type' => 'application/ld+json'] )); } } diff --git a/src/EventListener/FormatRequestListener.php b/src/EventListener/FormatRequestListener.php index c026a285512..1a02fe09bec 100644 --- a/src/EventListener/FormatRequestListener.php +++ b/src/EventListener/FormatRequestListener.php @@ -53,6 +53,17 @@ public function onKernelRequest(GetResponseEvent $event) } } - $request->attributes->set('_api_format', $mimeType ? $this->supportedFormats[$mimeType] : reset($this->supportedFormats)); + if ($mimeType) { + $request->attributes->set('_api_mime_type', $mimeType); + $request->attributes->set('_api_format', $this->supportedFormats[$mimeType]); + + return; + } + + reset($this->supportedFormats); + $format = each($this->supportedFormats); + + $request->attributes->set('_api_mime_type', $format['key']); + $request->attributes->set('_api_format', $format['value']); } } diff --git a/src/EventListener/ResponderViewListener.php b/src/EventListener/ResponderViewListener.php new file mode 100644 index 00000000000..7b1cfd4fb76 --- /dev/null +++ b/src/EventListener/ResponderViewListener.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Builds the response object. + * + * @author Kévin Dunglas + */ +final class ResponderViewListener +{ + const METHOD_TO_CODE = [ + Request::METHOD_POST => 201, + Request::METHOD_DELETE => 204, + ]; + + /** + * Creates a Response to send to the client according to the requested format. + */ + public function onKernelView(GetResponseForControllerResultEvent $event) + { + $controllerResult = $event->getControllerResult(); + $request = $event->getRequest(); + $mimeType = $request->attributes->get('_api_mime_type'); + + if ($controllerResult instanceof Response || !$mimeType) { + return; + } + + $event->setResponse(new Response( + $controllerResult, + self::METHOD_TO_CODE[$request->getMethod()] ?? 200, + ['Content-Type' => $request->attributes->get('_api_mime_type')] + )); + } +} diff --git a/src/EventListener/SerializerViewListener.php b/src/EventListener/SerializerViewListener.php new file mode 100644 index 00000000000..bd8eaaff4fa --- /dev/null +++ b/src/EventListener/SerializerViewListener.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Serializes data. + * + * @author Kévin Dunglas + */ +class SerializerViewListener +{ + private $serializer; + + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * Serializes the data to the requested format. + */ + public function onKernelView(GetResponseForControllerResultEvent $event) + { + $controllerResult = $event->getControllerResult(); + $request = $event->getRequest(); + $format = $request->attributes->get('_api_format'); + + if ($controllerResult instanceof Response || !$format) { + return; + } + + $resourceClass = $request->attributes->get('_resource_class'); + $collectionOperationName = $request->attributes->get('_collection_operation_name'); + $itemOperationName = $request->attributes->get('_item_operation_name'); + + if (!$resourceClass || (!$collectionOperationName && !$itemOperationName)) { + return; + } + + $context = ['request_uri' => $request->getRequestUri(), 'resource_class' => $resourceClass]; + if ($collectionOperationName) { + $context['collection_operation_name'] = $collectionOperationName; + } else { + $context['item_operation_name'] = $itemOperationName; + } + + $event->setControllerResult($this->serializer->serialize($controllerResult, $format, $context)); + } +} diff --git a/src/Hydra/Action/DocumentationAction.php b/src/Hydra/Action/DocumentationAction.php index 3e5c90db8e1..32371416805 100644 --- a/src/Hydra/Action/DocumentationAction.php +++ b/src/Hydra/Action/DocumentationAction.php @@ -12,6 +12,7 @@ namespace ApiPlatform\Core\Hydra\Action; use ApiPlatform\Core\Hydra\ApiDocumentationBuilderInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; /** @@ -30,15 +31,9 @@ public function __construct(ApiDocumentationBuilderInterface $apiDocumentationBu /** * Gets API doc. - * - * @param Request $request - * - * @return array */ - public function __invoke(Request $request) + public function __invoke(Request $request) : JsonResponse { - $request->attributes->set('_api_format', 'jsonld'); - - return $this->apiDocumentationBuilder->getApiDocumentation(); + return new JsonResponse($this->apiDocumentationBuilder->getApiDocumentation(), 200, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/Hydra/Action/ExceptionAction.php b/src/Hydra/Action/ExceptionAction.php index e3f0707973a..f19d642726a 100644 --- a/src/Hydra/Action/ExceptionAction.php +++ b/src/Hydra/Action/ExceptionAction.php @@ -12,8 +12,8 @@ namespace ApiPlatform\Core\Hydra\Action; use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\JsonLd\Response; use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Serializer\Exception\ExceptionInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -32,27 +32,21 @@ public function __construct(NormalizerInterface $normalizer) } /** - * Converts a {@see \Symfony\Component\Debug\Exception\FlattenException} - * to a {@see \ApiPlatform\Core\JsonLd\Response}. - * - * @param FlattenException $exception - * - * @return Response + * Converts a an exception to a JSON response. */ - public function __invoke(FlattenException $exception) + public function __invoke(FlattenException $exception) : JsonResponse { $exceptionClass = $exception->getClass(); if ( is_a($exceptionClass, ExceptionInterface::class, true) || is_a($exceptionClass, InvalidArgumentException::class, true) ) { - $exception->setStatusCode(Response::HTTP_BAD_REQUEST); + $exception->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); } - return new Response( - $this->normalizer->normalize($exception, 'hydra-error'), - $exception->getStatusCode(), - $exception->getHeaders() - ); + $headers = $exception->getHeaders(); + $headers['Content-Type'] = 'application/ld+json'; + + return new JsonResponse($this->normalizer->normalize($exception, 'hydra-error'), $exception->getStatusCode(), $headers); } } diff --git a/src/JsonLd/Action/ContextAction.php b/src/JsonLd/Action/ContextAction.php index c60c28b473d..c36b2e056f4 100644 --- a/src/JsonLd/Action/ContextAction.php +++ b/src/JsonLd/Action/ContextAction.php @@ -14,6 +14,7 @@ use ApiPlatform\Core\JsonLd\ContextBuilderInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -43,30 +44,23 @@ public function __construct(ContextBuilderInterface $contextBuilder, ResourceNam /** * Generates a context according to the type requested. * - * @param Request $request - * @param string $shortName - * * @throws NotFoundHttpException - * - * @return array */ - public function __invoke(Request $request, string $shortName) : array + public function __invoke(Request $request, string $shortName) : JsonResponse { - $request->attributes->set('_api_format', 'jsonld'); - if ('Entrypoint' === $shortName) { return ['@context' => $this->contextBuilder->getEntrypointContext()]; } if (isset(self::RESERVED_SHORT_NAMES[$shortName])) { - return ['@context' => $this->contextBuilder->getBaseContext()]; + return new JsonResponse(['@context' => $this->contextBuilder->getBaseContext()], 200, ['Content-Type' => 'application/ld+json']); } foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); if ($shortName === $resourceMetadata->getShortName()) { - return ['@context' => $this->contextBuilder->getResourceContext($resourceClass)]; + return new JsonResponse(['@context' => $this->contextBuilder->getResourceContext($resourceClass)], 200, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/JsonLd/Action/EntrypointAction.php b/src/JsonLd/Action/EntrypointAction.php index 2dda49c186e..5ee9ece1d3d 100644 --- a/src/JsonLd/Action/EntrypointAction.php +++ b/src/JsonLd/Action/EntrypointAction.php @@ -12,6 +12,7 @@ namespace ApiPlatform\Core\JsonLd\Action; use ApiPlatform\Core\JsonLd\EntrypointBuilderInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; /** @@ -28,17 +29,8 @@ public function __construct(EntrypointBuilderInterface $entrypointBuilder) $this->entrypointBuilder = $entrypointBuilder; } - /** - * Builds the entrypoint. - * - * @param Request $request - * - * @return array - */ - public function __invoke(Request $request) : array + public function __invoke(Request $request) : JsonResponse { - $request->attributes->set('_api_format', 'jsonld'); - - return $this->entrypointBuilder->getEntrypoint(); + return new JsonResponse($this->entrypointBuilder->getEntrypoint(), 200, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/JsonLd/EventListener/ResponderViewListener.php b/src/JsonLd/EventListener/ResponderViewListener.php deleted file mode 100644 index b6627b81a0c..00000000000 --- a/src/JsonLd/EventListener/ResponderViewListener.php +++ /dev/null @@ -1,92 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ApiPlatform\Core\JsonLd\EventListener; - -use ApiPlatform\Core\JsonLd\Response as JsonLdResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Normalizes data then builds the response object. - * - * @author Kévin Dunglas - */ -final class ResponderViewListener -{ - const FORMAT = 'jsonld'; - - private $normalizer; - - public function __construct(NormalizerInterface $normalizer) - { - $this->normalizer = $normalizer; - } - - /** - * In an API context, converts any data to a JSON-LD response. - * - * @param GetResponseForControllerResultEvent $event - * - * @return JsonLdResponse|mixed - */ - public function onKernelView(GetResponseForControllerResultEvent $event) - { - $controllerResult = $event->getControllerResult(); - - if ($controllerResult instanceof Response) { - return; - } - - $request = $event->getRequest(); - - $format = $request->attributes->get('_api_format'); - if (self::FORMAT !== $format) { - return; - } - - switch ($request->getMethod()) { - case Request::METHOD_POST: - $status = 201; - break; - - case Request::METHOD_DELETE: - $status = 204; - break; - - default: - $status = 200; - break; - } - - $resourceClass = $request->attributes->get('_resource_class'); - $collectionOperationName = $request->attributes->get('_collection_operation_name'); - $itemOperationName = $request->attributes->get('_item_operation_name'); - - if (!$resourceClass || (!$collectionOperationName && !$itemOperationName)) { - $event->setResponse(new JsonLdResponse($controllerResult, $status)); - - return; - } - - $context = ['request_uri' => $request->getRequestUri(), 'resource_class' => $resourceClass]; - if ($collectionOperationName) { - $context['collection_operation_name'] = $collectionOperationName; - } else { - $context['item_operation_name'] = $itemOperationName; - } - - $response = new JsonLdResponse($this->normalizer->normalize($controllerResult, self::FORMAT, $context), $status); - $event->setResponse($response); - } -} diff --git a/src/JsonLd/Response.php b/src/JsonLd/Response.php deleted file mode 100644 index 62b77d37b90..00000000000 --- a/src/JsonLd/Response.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ApiPlatform\Core\JsonLd; - -use Symfony\Component\HttpFoundation\JsonResponse; - -/** - * JSON-LD Response. - * - * @author Kévin Dunglas - */ -class Response extends JsonResponse -{ - /** - * {@inheritdoc} - */ - protected function update() - { - // Only set the header when there is none or when it equals 'application/ld+json' (from a previous update with callback) - // in order to not overwrite a custom definition. - if (!$this->headers->has('Content-Type') || 'application/ld+json' === $this->headers->get('Content-Type')) { - $this->headers->set('Content-Type', 'application/ld+json'); - } - - return $this->setContent($this->data); - } -} diff --git a/src/JsonLd/Serializer/JsonLdEncoder.php b/src/JsonLd/Serializer/JsonLdEncoder.php index 0e2e520004f..af2f37cf771 100644 --- a/src/JsonLd/Serializer/JsonLdEncoder.php +++ b/src/JsonLd/Serializer/JsonLdEncoder.php @@ -11,6 +11,9 @@ namespace ApiPlatform\Core\JsonLd\Serializer; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\Encoder\JsonEncoder; /** @@ -22,6 +25,13 @@ final class JsonLdEncoder extends JsonEncoder { const FORMAT = 'jsonld'; + public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) + { + // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. + $this->encodingImpl = $encodingImpl ?: new JsonEncode(JsonResponse::DEFAULT_ENCODING_OPTIONS); + $this->decodingImpl = $decodingImpl ?: new JsonDecode(true); + } + /** * {@inheritdoc} */ diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 5269694936a..ae7a314f3bc 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -259,8 +259,10 @@ private function getContainerBuilderProphecy() 'api_platform.routing.resource_path_generator.underscore', 'api_platform.routing.resource_path_generator.dash', 'api_platform.listener.request.format', - 'api_platform.listener.view.validation', + 'api_platform.listener.view.serializer', 'api_platform.listener.view.deserializer', + 'api_platform.listener.view.validation', + 'api_platform.listener.view.responder', 'api_platform.action.get_collection', 'api_platform.action.post_collection', 'api_platform.action.get_item', @@ -284,10 +286,8 @@ private function getContainerBuilderProphecy() 'api_platform.doctrine.listener.view.manager', 'api_platform.jsonld.entrypoint_builder', 'api_platform.jsonld.context_builder', - 'api_platform.jsonld.listener.view.responder', 'api_platform.jsonld.normalizer.item', 'api_platform.jsonld.encoder', - 'api_platform.jsonld.listener.view.responder', 'api_platform.jsonld.action.context', 'api_platform.jsonld.action.entrypoint', 'api_platform.hydra.documentation_builder', diff --git a/tests/EventListener/FormatRequestListenerTest.php b/tests/EventListener/FormatRequestListenerTest.php index 7c8356fa953..50228fa4927 100644 --- a/tests/EventListener/FormatRequestListenerTest.php +++ b/tests/EventListener/FormatRequestListenerTest.php @@ -33,6 +33,7 @@ public function testNoResourceClass() $listener->onKernelRequest($event); $this->assertFalse($request->attributes->has('_api_format')); + $this->assertFalse($request->attributes->has('_api_mime_type')); } public function testSupportedRequestFormat() @@ -49,6 +50,7 @@ public function testSupportedRequestFormat() $listener->onKernelRequest($event); $this->assertSame('xml', $request->attributes->get('_api_format')); + $this->assertSame('text/xml', $request->attributes->get('_api_mime_type')); } public function testUnsupportedRequestFormat() @@ -65,6 +67,7 @@ public function testUnsupportedRequestFormat() $listener->onKernelRequest($event); $this->assertSame('json', $request->attributes->get('_api_format')); + $this->assertSame('application/json', $request->attributes->get('_api_mime_type')); } public function testSupportedAcceptHeader() @@ -81,6 +84,7 @@ public function testSupportedAcceptHeader() $listener->onKernelRequest($event); $this->assertSame('json', $request->attributes->get('_api_format')); + $this->assertSame('application/json', $request->attributes->get('_api_mime_type')); } public function testUnsupportedAcceptHeader() @@ -97,5 +101,6 @@ public function testUnsupportedAcceptHeader() $listener->onKernelRequest($event); $this->assertSame('binary', $request->attributes->get('_api_format')); + $this->assertSame('application/octet-stream', $request->attributes->get('_api_mime_type')); } } diff --git a/tests/Fixtures/TestBundle/Controller/CustomController.php b/tests/Fixtures/TestBundle/Controller/CustomController.php index 1dea8cc0ace..2714ddc2c11 100644 --- a/tests/Fixtures/TestBundle/Controller/CustomController.php +++ b/tests/Fixtures/TestBundle/Controller/CustomController.php @@ -11,8 +11,8 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Controller; -use ApiPlatform\Core\JsonLd\Response; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\JsonResponse; /** * Custom Controller. @@ -21,11 +21,8 @@ */ class CustomController extends Controller { - /** - * @return \ApiPlatform\Core\JsonLd\Response - */ - public function customAction($id) + public function customAction(int $id) : JsonResponse { - return new Response(sprintf('This is a custom action for %d.', $id)); + return new JsonResponse(sprintf('This is a custom action for %d.', $id), 200, ['Content-Type' => 'application/ld+json']); } } diff --git a/tests/Fixtures/TestBundle/EventListener/XmlResponderViewListener.php b/tests/Fixtures/TestBundle/EventListener/XmlResponderViewListener.php deleted file mode 100644 index efa75bf0c14..00000000000 --- a/tests/Fixtures/TestBundle/EventListener/XmlResponderViewListener.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\EventListener; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; -use Symfony\Component\Serializer\SerializerInterface; - -/** - * Serializes data in XML then builds the response object. - * - * @author Kévin Dunglas - */ -class XmlResponderViewListener -{ - const FORMAT = 'xml'; - - /** - * @var SerializerInterface - */ - private $serializer; - - /** - * @var ResourceMetadataFactoryInterface - */ - private $resourceMetadataFactory; - - public function __construct(SerializerInterface $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - $this->serializer = $serializer; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * In an API context, converts any data to a XML response. - * - * @param GetResponseForControllerResultEvent $event - * - * @return Response|mixed - */ - public function onKernelView(GetResponseForControllerResultEvent $event) - { - $controllerResult = $event->getControllerResult(); - - if ($controllerResult instanceof Response) { - return $controllerResult; - } - - $request = $event->getRequest(); - - $format = $request->attributes->get('_api_format'); - if (self::FORMAT !== $format) { - return $controllerResult; - } - - switch ($request->getMethod()) { - case Request::METHOD_POST: - $status = 201; - break; - - case Request::METHOD_DELETE: - $status = 204; - break; - - default: - $status = 200; - break; - } - - $resourceClass = $request->attributes->get('_resource_class'); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $context = $resourceMetadata->getAttribute('normalization_context', []); - - $response = new Response( - $this->serializer->serialize($controllerResult, self::FORMAT, $context), - $status, - ['Content-Type' => 'application/xml'] - ); - - $event->setResponse($response); - } -} diff --git a/tests/Fixtures/app/config/config.yml b/tests/Fixtures/app/config/config.yml index 782b3cb1e4a..78a8c7017ad 100644 --- a/tests/Fixtures/app/config/config.yml +++ b/tests/Fixtures/app/config/config.yml @@ -65,15 +65,6 @@ services: app.name_converter: class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter' - app.xml_responder_view_listener: - class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\EventListener\XmlResponderViewListener' - arguments: - - '@api_platform.serializer' - - - '@api_platform.metadata.resource.metadata_factory' - tags: - - { name: 'kernel.event_listener', event: 'kernel.view', method: 'onKernelView' } - app.my_dummy_resource.search_filter: parent: 'api_platform.doctrine.orm.search_filter' arguments: [ { 'id': 'exact', 'name': 'partial', 'alias': 'start', 'description': 'word_start', 'relatedDummy.name': 'exact', 'relatedDummies': 'exact', 'dummy': 'ipartial' } ] From 5a096ababf26ceed83ae8902c29a57f4cfb39311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 3 Jul 2016 09:59:07 +0200 Subject: [PATCH 02/10] Add tests for Responder and Serializer view listeners --- src/EventListener/ResponderViewListener.php | 6 +- src/Hydra/Action/DocumentationAction.php | 4 +- src/JsonLd/Action/ContextAction.php | 7 +- src/JsonLd/Action/EntrypointAction.php | 5 +- .../ResponderViewListenerTest.php | 110 ++++++++++++++++++ .../SerializerViewListenerTest.php | 107 +++++++++++++++++ 6 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 tests/EventListener/ResponderViewListenerTest.php create mode 100644 tests/EventListener/SerializerViewListenerTest.php diff --git a/src/EventListener/ResponderViewListener.php b/src/EventListener/ResponderViewListener.php index 7b1cfd4fb76..be8828fcf38 100644 --- a/src/EventListener/ResponderViewListener.php +++ b/src/EventListener/ResponderViewListener.php @@ -23,8 +23,8 @@ final class ResponderViewListener { const METHOD_TO_CODE = [ - Request::METHOD_POST => 201, - Request::METHOD_DELETE => 204, + Request::METHOD_POST => Response::HTTP_CREATED, + Request::METHOD_DELETE => Response::HTTP_NO_CONTENT, ]; /** @@ -42,7 +42,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event) $event->setResponse(new Response( $controllerResult, - self::METHOD_TO_CODE[$request->getMethod()] ?? 200, + self::METHOD_TO_CODE[$request->getMethod()] ?? Response::HTTP_OK, ['Content-Type' => $request->attributes->get('_api_mime_type')] )); } diff --git a/src/Hydra/Action/DocumentationAction.php b/src/Hydra/Action/DocumentationAction.php index 32371416805..aaebeee6264 100644 --- a/src/Hydra/Action/DocumentationAction.php +++ b/src/Hydra/Action/DocumentationAction.php @@ -32,8 +32,8 @@ public function __construct(ApiDocumentationBuilderInterface $apiDocumentationBu /** * Gets API doc. */ - public function __invoke(Request $request) : JsonResponse + public function __invoke() : JsonResponse { - return new JsonResponse($this->apiDocumentationBuilder->getApiDocumentation(), 200, ['Content-Type' => 'application/ld+json']); + return new json_encode($this->apiDocumentationBuilder->getApiDocumentation(), JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/JsonLd/Action/ContextAction.php b/src/JsonLd/Action/ContextAction.php index c36b2e056f4..5e4998972a9 100644 --- a/src/JsonLd/Action/ContextAction.php +++ b/src/JsonLd/Action/ContextAction.php @@ -15,7 +15,6 @@ use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -46,21 +45,21 @@ public function __construct(ContextBuilderInterface $contextBuilder, ResourceNam * * @throws NotFoundHttpException */ - public function __invoke(Request $request, string $shortName) : JsonResponse + public function __invoke(string $shortName) : JsonResponse { if ('Entrypoint' === $shortName) { return ['@context' => $this->contextBuilder->getEntrypointContext()]; } if (isset(self::RESERVED_SHORT_NAMES[$shortName])) { - return new JsonResponse(['@context' => $this->contextBuilder->getBaseContext()], 200, ['Content-Type' => 'application/ld+json']); + return new JsonResponse(['@context' => $this->contextBuilder->getBaseContext()], JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); } foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); if ($shortName === $resourceMetadata->getShortName()) { - return new JsonResponse(['@context' => $this->contextBuilder->getResourceContext($resourceClass)], 200, ['Content-Type' => 'application/ld+json']); + return new JsonResponse(['@context' => $this->contextBuilder->getResourceContext($resourceClass)], JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/JsonLd/Action/EntrypointAction.php b/src/JsonLd/Action/EntrypointAction.php index 5ee9ece1d3d..8094c10f67a 100644 --- a/src/JsonLd/Action/EntrypointAction.php +++ b/src/JsonLd/Action/EntrypointAction.php @@ -13,7 +13,6 @@ use ApiPlatform\Core\JsonLd\EntrypointBuilderInterface; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; /** * Generates the JSON-LD API entrypoint. @@ -29,8 +28,8 @@ public function __construct(EntrypointBuilderInterface $entrypointBuilder) $this->entrypointBuilder = $entrypointBuilder; } - public function __invoke(Request $request) : JsonResponse + public function __invoke() : JsonResponse { - return new JsonResponse($this->entrypointBuilder->getEntrypoint(), 200, ['Content-Type' => 'application/ld+json']); + return new JsonResponse($this->entrypointBuilder->getEntrypoint(), JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); } } diff --git a/tests/EventListener/ResponderViewListenerTest.php b/tests/EventListener/ResponderViewListenerTest.php new file mode 100644 index 00000000000..dbaab6711b8 --- /dev/null +++ b/tests/EventListener/ResponderViewListenerTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\EventListener; + +use ApiPlatform\Core\EventListener\ResponderViewListener; +use Prophecy\Argument; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @author Kévin Dunglas + */ +class ResponderViewListenerTest extends \PHPUnit_Framework_TestCase +{ + public function testDoNotHandleResponse() + { + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new Response()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_mime_type' => 'text/xml'])); + + $listener = new ResponderViewListener(); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testDoNotHandleWhenMimeTypeNotSet() + { + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn('foo'); + $eventProphecy->getRequest()->willReturn(new Request()); + + $listener = new ResponderViewListener(); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testCreate200Response() + { + $kernelProphecy = $this->prophesize(HttpKernelInterface::class); + $event = new GetResponseForControllerResultEvent( + $kernelProphecy->reveal(), + new Request([], [], ['_api_mime_type' => 'text/xml']), + HttpKernelInterface::MASTER_REQUEST, + 'foo' + ); + + $listener = new ResponderViewListener(); + $listener->onKernelView($event); + + $response = $event->getResponse(); + $this->assertEquals('foo', $response->getContent()); + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertEquals('text/xml', $response->headers->get('Content-Type')); + } + + public function testCreate201Response() + { + $kernelProphecy = $this->prophesize(HttpKernelInterface::class); + + $request = new Request([], [], ['_api_mime_type' => 'text/xml']); + $request->setMethod(Request::METHOD_POST); + + $event = new GetResponseForControllerResultEvent( + $kernelProphecy->reveal(), + $request, + HttpKernelInterface::MASTER_REQUEST, + 'foo' + ); + + $listener = new ResponderViewListener(); + $listener->onKernelView($event); + + $response = $event->getResponse(); + $this->assertEquals('foo', $response->getContent()); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertEquals('text/xml', $response->headers->get('Content-Type')); + } + + public function testCreate204Response() + { + $kernelProphecy = $this->prophesize(HttpKernelInterface::class); + + $request = new Request([], [], ['_api_mime_type' => 'text/xml']); + $request->setMethod(Request::METHOD_DELETE); + + $event = new GetResponseForControllerResultEvent( + $kernelProphecy->reveal(), + $request, + HttpKernelInterface::MASTER_REQUEST, + 'foo' + ); + + $listener = new ResponderViewListener(); + $listener->onKernelView($event); + + $response = $event->getResponse(); + $this->assertEquals('foo', $response->getContent()); + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + $this->assertEquals('text/xml', $response->headers->get('Content-Type')); + } +} diff --git a/tests/EventListener/SerializerViewListenerTest.php b/tests/EventListener/SerializerViewListenerTest.php new file mode 100644 index 00000000000..d3b0d526e1f --- /dev/null +++ b/tests/EventListener/SerializerViewListenerTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\EventListener; + +use ApiPlatform\Core\EventListener\SerializerViewListener; +use Prophecy\Argument; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * @author Kévin Dunglas + */ +class SerializerViewListenerTest extends \PHPUnit_Framework_TestCase +{ + public function testDoNotSerializeResponse() + { + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new Response()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml'])); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testDoNotSerializeWhenFormatNotSet() + { + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getRequest()->willReturn(new Request()); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testDoNotSerializeWhenResourceClassNotSet() + { + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_collection_operation_name' => 'get'])); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testDoNotSerializeWhenOperationNotSet() + { + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo'])); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testSerializeCollectionOperation() + { + $expectedContext = ['request_uri' => '', 'resource_class' => 'Foo', 'collection_operation_name' => 'get']; + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), $expectedContext)->willReturn('bar')->shouldBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_collection_operation_name' => 'get'])); + $eventProphecy->setControllerResult('bar')->shouldBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } + + public function testSerializeItemOperation() + { + $expectedContext = ['request_uri' => '', 'resource_class' => 'Foo', 'item_operation_name' => 'get']; + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize(Argument::any(), Argument::type('string'), $expectedContext)->willReturn('bar')->shouldBeCalled(); + + $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); + $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_item_operation_name' => 'get'])); + $eventProphecy->setControllerResult('bar')->shouldBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal()); + $listener->onKernelView($eventProphecy->reveal()); + } +} From 9b58ec542aace40e31cd3ef0c60bbf499c25b8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 3 Jul 2016 10:15:16 +0200 Subject: [PATCH 03/10] Use composition for the JsonLdEncoder --- src/Hydra/Action/DocumentationAction.php | 3 +- src/JsonLd/Serializer/JsonLdEncoder.php | 29 ++++++++-- .../ResponderViewListenerTest.php | 1 - tests/JsonLd/Serializer/JsonLdEncoderTest.php | 54 +++++++++++++++++++ 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 tests/JsonLd/Serializer/JsonLdEncoderTest.php diff --git a/src/Hydra/Action/DocumentationAction.php b/src/Hydra/Action/DocumentationAction.php index aaebeee6264..a16cc15f516 100644 --- a/src/Hydra/Action/DocumentationAction.php +++ b/src/Hydra/Action/DocumentationAction.php @@ -13,7 +13,6 @@ use ApiPlatform\Core\Hydra\ApiDocumentationBuilderInterface; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; /** * Generates the Hydra API documentation. @@ -34,6 +33,6 @@ public function __construct(ApiDocumentationBuilderInterface $apiDocumentationBu */ public function __invoke() : JsonResponse { - return new json_encode($this->apiDocumentationBuilder->getApiDocumentation(), JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); + return new JsonResponse($this->apiDocumentationBuilder->getApiDocumentation(), JsonResponse::HTTP_OK, ['Content-Type' => 'application/ld+json']); } } diff --git a/src/JsonLd/Serializer/JsonLdEncoder.php b/src/JsonLd/Serializer/JsonLdEncoder.php index af2f37cf771..ba33f495bf9 100644 --- a/src/JsonLd/Serializer/JsonLdEncoder.php +++ b/src/JsonLd/Serializer/JsonLdEncoder.php @@ -12,6 +12,8 @@ namespace ApiPlatform\Core\JsonLd\Serializer; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\JsonDecode; use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\Encoder\JsonEncoder; @@ -21,15 +23,18 @@ * * @author Kévin Dunglas */ -final class JsonLdEncoder extends JsonEncoder +final class JsonLdEncoder implements EncoderInterface, DecoderInterface { const FORMAT = 'jsonld'; - public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) + private $jsonEncoder; + + public function __construct(JsonEncoder $jsonEncoder = null) { // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. - $this->encodingImpl = $encodingImpl ?: new JsonEncode(JsonResponse::DEFAULT_ENCODING_OPTIONS); - $this->decodingImpl = $decodingImpl ?: new JsonDecode(true); + $this->jsonEncoder = $jsonEncoder ?: new JsonEncoder( + new JsonEncode(JsonResponse::DEFAULT_ENCODING_OPTIONS), new JsonDecode(true) + ); } /** @@ -40,6 +45,14 @@ public function supportsEncoding($format) return self::FORMAT === $format; } + /** + * {@inheritdoc} + */ + public function encode($data, $format, array $context = []) + { + return $this->jsonEncoder->encode($data, $format, $context); + } + /** * {@inheritdoc} */ @@ -47,4 +60,12 @@ public function supportsDecoding($format) { return self::FORMAT === $format; } + + /** + * {@inheritdoc} + */ + public function decode($data, $format, array $context = []) + { + return $this->jsonEncoder->decode($data, $format, $context); + } } diff --git a/tests/EventListener/ResponderViewListenerTest.php b/tests/EventListener/ResponderViewListenerTest.php index dbaab6711b8..d91d078efea 100644 --- a/tests/EventListener/ResponderViewListenerTest.php +++ b/tests/EventListener/ResponderViewListenerTest.php @@ -12,7 +12,6 @@ namespace ApiPlatform\Core\Tests\EventListener; use ApiPlatform\Core\EventListener\ResponderViewListener; -use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; diff --git a/tests/JsonLd/Serializer/JsonLdEncoderTest.php b/tests/JsonLd/Serializer/JsonLdEncoderTest.php new file mode 100644 index 00000000000..bd667a19215 --- /dev/null +++ b/tests/JsonLd/Serializer/JsonLdEncoderTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\JsonLd\Serializer; + +use ApiPlatform\Core\JsonLd\Serializer\JsonLdEncoder; + +/** + * @author Kévin Dunglas + */ +class JsonLdEncoderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var JsonLdEncoder + */ + private $encoder; + + public function setUp() + { + $this->encoder = new JsonLdEncoder(); + } + + public function testSupportEncoding() + { + $this->assertTrue($this->encoder->supportsEncoding(JsonLdEncoder::FORMAT)); + $this->assertFalse($this->encoder->supportsEncoding('csv')); + } + + public function testEncode() + { + $data = ['foo' => 'bar']; + + $this->assertEquals('{"foo":"bar"}', $this->encoder->encode($data, JsonLdEncoder::FORMAT)); + } + + public function testSupportDecoding() + { + $this->assertTrue($this->encoder->supportsDecoding(JsonLdEncoder::FORMAT)); + $this->assertFalse($this->encoder->supportsDecoding('csv')); + } + + public function testDecode() + { + $this->assertEquals(['foo' => 'bar'], $this->encoder->decode('{"foo":"bar"}', JsonLdEncoder::FORMAT)); + } +} From 0695a9c06d092d6d1824ed9090f2eae19ca3b8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 3 Jul 2016 11:02:44 +0200 Subject: [PATCH 04/10] Rename the supported_formats config key to formats --- .../DependencyInjection/ApiPlatformExtension.php | 8 ++++---- .../Bundle/DependencyInjection/Configuration.php | 2 +- .../Symfony/Bundle/Resources/config/api.xml | 2 +- src/EventListener/FormatRequestListener.php | 16 ++++++++-------- .../ApiPlatformExtensionTest.php | 2 +- .../DependencyInjection/ConfigurationTest.php | 2 +- tests/Fixtures/app/config/config.yml | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 0467ba1480a..a624db8af43 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -52,10 +52,10 @@ public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $supportedFormats = []; - foreach ($config['supported_formats'] as $format => $value) { + $formats = []; + foreach ($config['formats'] as $format => $value) { foreach ($value['mime_types'] as $mimeType) { - $supportedFormats[$mimeType] = $format; + $formats[$mimeType] = $format; } } @@ -67,7 +67,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('api_platform.title', $config['title']); $container->setParameter('api_platform.description', $config['description']); - $container->setParameter('api_platform.supported_formats', $supportedFormats); + $container->setParameter('api_platform.formats', $formats); $container->setParameter('api_platform.collection.order', $config['collection']['order']); $container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']); $container->setParameter('api_platform.collection.pagination.enabled', $config['collection']['pagination']['enabled']); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index ce73047c2fb..d20cf12d58c 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -33,7 +33,7 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('title')->defaultValue('')->info('The title of the API.')->end() ->scalarNode('description')->defaultValue('')->info('The description of the API.')->end() - ->arrayNode('supported_formats') + ->arrayNode('formats') ->defaultValue(['jsonld' => ['mime_types' => ['application/ld+json']]]) ->info('The list of enabled formats. The first one will be the default.') ->normalizeKeys(false) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 9195faafccc..6cae3947511 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -53,7 +53,7 @@ - %api_platform.supported_formats% + %api_platform.formats% diff --git a/src/EventListener/FormatRequestListener.php b/src/EventListener/FormatRequestListener.php index 1a02fe09bec..4a3f6a67cab 100644 --- a/src/EventListener/FormatRequestListener.php +++ b/src/EventListener/FormatRequestListener.php @@ -22,12 +22,12 @@ final class FormatRequestListener { private $negotiator; - private $supportedFormats; + private $formats; - public function __construct(Negotiator $negotiator, array $supportedFormats) + public function __construct(Negotiator $negotiator, array $formats) { $this->negotiator = $negotiator; - $this->supportedFormats = $supportedFormats; + $this->formats = $formats; } /** @@ -43,25 +43,25 @@ public function onKernelRequest(GetResponseEvent $event) // Use the Symfony request format if available and applicable $requestFormat = $request->getRequestFormat(null); $mimeType = $requestFormat ? $request->getMimeType($requestFormat) : null; - if (null === $mimeType || !in_array($mimeType, $this->supportedFormats)) { + if (null === $mimeType || !in_array($mimeType, $this->formats)) { if (null === $accept = $request->headers->get('Accept')) { $mimeType = null; } else { // Try to guess the best format to use - $acceptHeader = $this->negotiator->getBest($accept, array_keys($this->supportedFormats)); + $acceptHeader = $this->negotiator->getBest($accept, array_keys($this->formats)); $mimeType = $acceptHeader ? $acceptHeader->getType() : null; } } if ($mimeType) { $request->attributes->set('_api_mime_type', $mimeType); - $request->attributes->set('_api_format', $this->supportedFormats[$mimeType]); + $request->attributes->set('_api_format', $this->formats[$mimeType]); return; } - reset($this->supportedFormats); - $format = each($this->supportedFormats); + reset($this->formats); + $format = each($this->formats); $request->attributes->set('_api_mime_type', $format['key']); $request->attributes->set('_api_format', $format['value']); diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index ae7a314f3bc..4f16d5cdd90 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -168,7 +168,7 @@ private function getContainerBuilderProphecy() $parameters = [ 'api_platform.title' => 'title', 'api_platform.description' => 'description', - 'api_platform.supported_formats' => ['application/ld+json' => 'jsonld'], + 'api_platform.formats' => ['application/ld+json' => 'jsonld'], 'api_platform.collection.order' => null, 'api_platform.collection.order_parameter_name' => 'order', 'api_platform.collection.pagination.enabled' => true, diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index df2f69c0588..4fcf1dd6d41 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -33,7 +33,7 @@ public function testDefaultConfig() $this->assertEquals([ 'title' => 'title', 'description' => 'description', - 'supported_formats' => ['jsonld' => ['mime_types' => ['application/ld+json']]], + 'formats' => ['jsonld' => ['mime_types' => ['application/ld+json']]], 'routing' => [ 'resource_path_generator' => 'api_platform.routing.resource_path_generator.underscore', ], diff --git a/tests/Fixtures/app/config/config.yml b/tests/Fixtures/app/config/config.yml index 78a8c7017ad..a2f1c91c047 100644 --- a/tests/Fixtures/app/config/config.yml +++ b/tests/Fixtures/app/config/config.yml @@ -29,7 +29,7 @@ doctrine: api_platform: title: 'My Dummy API' description: 'This is a test API.' - supported_formats: + formats: jsonld: ['application/ld+json'] xml: ['application/xml', 'text/xml'] name_converter: 'app.name_converter' From 22cf3836f2e7d00417ed6f2ae60f45347e853a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 3 Jul 2016 23:03:39 +0200 Subject: [PATCH 05/10] Extract context generation --- .../Symfony/Bundle/Resources/config/api.xml | 6 +++-- ...Listener.php => ValidatorViewListener.php} | 8 +++---- .../DeserializerViewListener.php | 21 +++++++++++++----- src/EventListener/SerializerViewListener.php | 22 +++++++++++++------ .../ApiPlatformExtensionTest.php | 2 +- ...Test.php => ValidatorViewListenerTest.php} | 15 +++++++------ .../Entity/RelatedToDummyFriend.php | 16 +++++++------- 7 files changed, 55 insertions(+), 35 deletions(-) rename src/Bridge/Symfony/Validator/EventListener/{ViewListener.php => ValidatorViewListener.php} (94%) rename tests/Bridge/Symfony/Validator/EventListener/{ViewListenerTest.php => ValidatorViewListenerTest.php} (83%) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 6cae3947511..702f20df78b 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -60,19 +60,21 @@ + + - - + + diff --git a/src/Bridge/Symfony/Validator/EventListener/ViewListener.php b/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php similarity index 94% rename from src/Bridge/Symfony/Validator/EventListener/ViewListener.php rename to src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php index 41c15bbfa2d..53522740360 100644 --- a/src/Bridge/Symfony/Validator/EventListener/ViewListener.php +++ b/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php @@ -22,15 +22,15 @@ * * @author Kévin Dunglas */ -final class ViewListener +final class ValidatorViewListener { - private $resourceMetadataFactory; private $validator; + private $resourceMetadataFactory; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ValidatorInterface $validator) + public function __construct(ValidatorInterface $validator, ResourceMetadataFactoryInterface $resourceMetadataFactory) { - $this->resourceMetadataFactory = $resourceMetadataFactory; $this->validator = $validator; + $this->resourceMetadataFactory = $resourceMetadataFactory; } /** diff --git a/src/EventListener/DeserializerViewListener.php b/src/EventListener/DeserializerViewListener.php index 43dda05b79d..a6902c65207 100644 --- a/src/EventListener/DeserializerViewListener.php +++ b/src/EventListener/DeserializerViewListener.php @@ -13,6 +13,7 @@ use ApiPlatform\Core\Api\RequestAttributesExtractor; use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; @@ -26,10 +27,12 @@ final class DeserializerViewListener { private $serializer; + private $resourceMetadataFactory; - public function __construct(SerializerInterface $serializer) + public function __construct(SerializerInterface $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory) { $this->serializer = $serializer; + $this->resourceMetadataFactory = $resourceMetadataFactory; } public function onKernelView(GetResponseForControllerResultEvent $event) @@ -42,18 +45,24 @@ public function onKernelView(GetResponseForControllerResultEvent $event) } try { - list($resourceClass, $collectionOperation, $itemOperation, $format) = RequestAttributesExtractor::extractAttributes($request); + list($resourceClass, $collectionOperationName, $itemOperationName, $format) = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } - $context = ['resource_class' => $resourceClass]; - if ($collectionOperation) { - $context['collection_operation_name'] = $collectionOperation; + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + + if ($collectionOperationName) { + $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, 'denormalization_context', [], true); + $context['collection_operation_name'] = $collectionOperationName; } else { - $context['item_operation_name'] = $itemOperation; + $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, 'denormalization_context', [], true); + $context['item_operation_name'] = $itemOperationName; } + $context['resource_class'] = $resourceClass; + $context['request_uri'] = $request->getRequestUri(); + if (null !== $data) { $context['object_to_populate'] = $data; } diff --git a/src/EventListener/SerializerViewListener.php b/src/EventListener/SerializerViewListener.php index bd8eaaff4fa..5b620646b85 100644 --- a/src/EventListener/SerializerViewListener.php +++ b/src/EventListener/SerializerViewListener.php @@ -11,6 +11,8 @@ namespace ApiPlatform\Core\EventListener; +use ApiPlatform\Core\Api\RequestAttributesExtractor; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\Serializer\SerializerInterface; @@ -22,10 +24,12 @@ class SerializerViewListener { private $serializer; + private $resourceMetadataFactory; - public function __construct(SerializerInterface $serializer) + public function __construct(SerializerInterface $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory) { $this->serializer = $serializer; + $this->resourceMetadataFactory = $resourceMetadataFactory; } /** @@ -41,21 +45,25 @@ public function onKernelView(GetResponseForControllerResultEvent $event) return; } - $resourceClass = $request->attributes->get('_resource_class'); - $collectionOperationName = $request->attributes->get('_collection_operation_name'); - $itemOperationName = $request->attributes->get('_item_operation_name'); - - if (!$resourceClass || (!$collectionOperationName && !$itemOperationName)) { + try { + list($resourceClass, $collectionOperationName, $itemOperationName, $format) = RequestAttributesExtractor::extractAttributes($request); + } catch (RuntimeException $e) { return; } - $context = ['request_uri' => $request->getRequestUri(), 'resource_class' => $resourceClass]; + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + if ($collectionOperationName) { + $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, 'normalization_context', [], true); $context['collection_operation_name'] = $collectionOperationName; } else { + $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, 'normalization_context', [], true); $context['item_operation_name'] = $itemOperationName; } + $context['resource_class'] = $resourceClass; + $context['request_uri'] = $request->getRequestUri(); + $event->setControllerResult($this->serializer->serialize($controllerResult, $format, $context)); } } diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 4f16d5cdd90..651b86a8e51 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -261,7 +261,7 @@ private function getContainerBuilderProphecy() 'api_platform.listener.request.format', 'api_platform.listener.view.serializer', 'api_platform.listener.view.deserializer', - 'api_platform.listener.view.validation', + 'api_platform.listener.view.validator', 'api_platform.listener.view.responder', 'api_platform.action.get_collection', 'api_platform.action.post_collection', diff --git a/tests/Bridge/Symfony/Validator/EventListener/ViewListenerTest.php b/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php similarity index 83% rename from tests/Bridge/Symfony/Validator/EventListener/ViewListenerTest.php rename to tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php index f5bf2ff368a..0667480c287 100644 --- a/tests/Bridge/Symfony/Validator/EventListener/ViewListenerTest.php +++ b/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php @@ -11,7 +11,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Symfony\Validator\EventListener; -use ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ViewListener; +use ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidatorViewListener; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Tests\Fixtures\DummyEntity; @@ -19,24 +19,25 @@ use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; /** * @author Samuel ROZE */ -class ViewListenerTest extends \PHPUnit_Framework_TestCase +class ValidatorViewListenerTest extends \PHPUnit_Framework_TestCase { public function testValidatorIsCalled() { $data = new DummyEntity(); $expectedValidationGroups = ['a', 'b', 'c']; - $validatorProphecy = $this->prophesize('Symfony\Component\Validator\Validator\ValidatorInterface'); + $validatorProphecy = $this->prophesize(ValidatorInterface::class); $validatorProphecy->validate($data, null, $expectedValidationGroups)->shouldBeCalled(); $validator = $validatorProphecy->reveal(); list($resourceMetadataFactory, $event) = $this->createEventObject($expectedValidationGroups, $data); - $validationViewListener = new ViewListener($resourceMetadataFactory, $validator); + $validationViewListener = new ValidatorViewListener($validator, $resourceMetadataFactory); $validationViewListener->onKernelView($event); } @@ -52,13 +53,13 @@ public function testThrowsValidationExceptionWithViolationsFound() $violationsProphecy->count()->willReturn(1); $violations = $violationsProphecy->reveal(); - $validatorProphecy = $this->prophesize('Symfony\Component\Validator\Validator\ValidatorInterface'); + $validatorProphecy = $this->prophesize(ValidatorInterface::class); $validatorProphecy->validate($data, null, $expectedValidationGroups)->shouldBeCalled()->willReturn($violations); $validator = $validatorProphecy->reveal(); list($resourceMetadataFactory, $event) = $this->createEventObject($expectedValidationGroups, $data); - $validationViewListener = new ViewListener($resourceMetadataFactory, $validator); + $validationViewListener = new ValidatorViewListener($validator, $resourceMetadataFactory); $validationViewListener->onKernelView($event); } @@ -80,7 +81,7 @@ private function createEventObject($expectedValidationGroups, $data) $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata); $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $kernel = $this->prophesize('Symfony\Component\HttpKernel\HttpKernelInterface')->reveal(); + $kernel = $this->prophesize(HttpKernelInterface::class)->reveal(); $request = new Request([], [], [ '_resource_class' => DummyEntity::class, '_item_operation_name' => 'create', diff --git a/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php b/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php index ef432b45bc2..11dfaf4fcc1 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php @@ -61,9 +61,9 @@ public function getName() } /** - * Get dummyFriend. + * Gets dummyFriend. * - * @return dummyFriend. + * @return DummyFriend */ public function getDummyFriend() { @@ -71,9 +71,9 @@ public function getDummyFriend() } /** - * Set dummyFriend. + * Sets dummyFriend. * - * @param dummyFriend the value to set. + * @param $dummyFriend the value to set. */ public function setDummyFriend($dummyFriend) { @@ -81,9 +81,9 @@ public function setDummyFriend($dummyFriend) } /** - * Get relatedDummy. + * Gets relatedDummy. * - * @return relatedDummy. + * @return RelatedDummy */ public function getRelatedDummy() { @@ -91,9 +91,9 @@ public function getRelatedDummy() } /** - * Set relatedDummy. + * Sets relatedDummy. * - * @param relatedDummy the value to set. + * @param $relatedDummy the value to set. */ public function setRelatedDummy($relatedDummy) { From 854c305e91479e6668455728fa5de162f224faf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 4 Jul 2016 10:33:55 +0200 Subject: [PATCH 06/10] Improve the overrode operation feature --- ...e.feature => overriding_operation.feature} | 61 ++++++++++--------- ...teDummy.php => OverrodeOperationDummy.php} | 48 +++++++-------- 2 files changed, 55 insertions(+), 54 deletions(-) rename features/{overriding_attribute.feature => overriding_operation.feature} (60%) rename tests/Fixtures/TestBundle/Entity/{CustomAttributeDummy.php => OverrodeOperationDummy.php} (62%) diff --git a/features/overriding_attribute.feature b/features/overriding_operation.feature similarity index 60% rename from features/overriding_attribute.feature rename to features/overriding_operation.feature index 8ebb11d4b98..9b5155d7629 100644 --- a/features/overriding_attribute.feature +++ b/features/overriding_operation.feature @@ -1,14 +1,14 @@ -Feature: Create-Retrieve-Update-Delete with custom attribute +Feature: Create-Retrieve-Update-Delete with a Overrode Operation context In order to use an hypermedia API As a client software developer I need to be able to retrieve, create, update and delete JSON-LD encoded resources. @createSchema Scenario: Create a resource - When I send a "POST" request to "/custom_attribute_dummies" with body: + When I send a "POST" request to "/overrode_operation_dummies" with body: """ { - "name": "My Custom Attribute Dummy", + "name": "My Overrode Operation Dummy", "description" : "Gerard", "alias": "notWritable" } @@ -19,52 +19,52 @@ Feature: Create-Retrieve-Update-Delete with custom attribute And the JSON should be equal to: """ { - "@context": "/contexts/CustomAttributeDummy", - "@id": "/custom_attribute_dummies/1", - "@type": "CustomAttributeDummy", - "name": "My Custom Attribute Dummy", + "@context": "/contexts/OverrodeOperationDummy", + "@id": "/overrode_operation_dummies/1", + "@type": "OverrodeOperationDummy", + "name": "My Overrode Operation Dummy", "alias": null, "description": "Gerard" } """ Scenario: Get a resource - When I send a "GET" request to "/custom_attribute_dummies/1" + When I send a "GET" request to "/overrode_operation_dummies/1" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/CustomAttributeDummy", - "@id": "/custom_attribute_dummies/1", - "@type": "CustomAttributeDummy", - "name": "My Custom Attribute Dummy", + "@context": "/contexts/OverrodeOperationDummy", + "@id": "/overrode_operation_dummies/1", + "@type": "OverrodeOperationDummy", + "name": "My Overrode Operation Dummy", "alias": null, "description": "Gerard" } """ Scenario: Get a not found exception - When I send a "GET" request to "/custom_attribute_dummies/42" + When I send a "GET" request to "/overrode_operation_dummies/42" Then the response status code should be 404 Scenario: Get a collection - When I send a "GET" request to "/custom_attribute_dummies" + When I send a "GET" request to "/overrode_operation_dummies" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/CustomAttributeDummy", - "@id": "\/custom_attribute_dummies", + "@context": "/contexts/OverrodeOperationDummy", + "@id": "\/overrode_operation_dummies", "@type": "hydra:Collection", "hydra:member": [ { - "@id": "\/custom_attribute_dummies\/1", - "@type": "CustomAttributeDummy", - "name": "My Custom Attribute Dummy", + "@id": "\/overrode_operation_dummies\/1", + "@type": "OverrodeOperationDummy", + "name": "My Overrode Operation Dummy", "alias": null, "description": "Gerard" } @@ -74,10 +74,10 @@ Feature: Create-Retrieve-Update-Delete with custom attribute """ Scenario: Update a resource - When I send a "PUT" request to "/custom_attribute_dummies/1" with body: + When I send a "PUT" request to "/overrode_operation_dummies/1" with body: """ { - "@id": "/custom_attribute_dummies/1", + "@id": "/overrode_operation_dummies/1", "name": "A nice dummy", "alias": "Dummy" } @@ -88,32 +88,33 @@ Feature: Create-Retrieve-Update-Delete with custom attribute And the JSON should be equal to: """ { - "@context": "/contexts/CustomAttributeDummy", - "@id": "/custom_attribute_dummies/1", - "@type": "CustomAttributeDummy", + "@context": "/contexts/OverrodeOperationDummy", + "@id": "/overrode_operation_dummies/1", + "@type": "OverrodeOperationDummy", "alias": "Dummy", "description": "Gerard" } """ Scenario: Get the final resource - When I send a "GET" request to "/custom_attribute_dummies/1" + When I send a "GET" request to "/overrode_operation_dummies/1" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/CustomAttributeDummy", - "@id": "/custom_attribute_dummies/1", - "@type": "CustomAttributeDummy", - "name": "My Custom Attribute Dummy", + "@context": "/contexts/OverrodeOperationDummy", + "@id": "/overrode_operation_dummies/1", + "@type": "OverrodeOperationDummy", + "name": "My Overrode Operation Dummy", "alias": "Dummy", "description": "Gerard" } """ + @dropSchema Scenario: Delete a resource - When I send a "DELETE" request to "/custom_attribute_dummies/1" + When I send a "DELETE" request to "/overrode_operation_dummies/1" Then the response status code should be 204 And the response should be empty diff --git a/tests/Fixtures/TestBundle/Entity/CustomAttributeDummy.php b/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php similarity index 62% rename from tests/Fixtures/TestBundle/Entity/CustomAttributeDummy.php rename to tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php index 942d2b5a25a..c8858cb3ae2 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomAttributeDummy.php +++ b/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php @@ -18,29 +18,32 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * Custom Attribute Dummy. + * Overrode Operation Dummy. * - * @author Amrouche Hamza - * - * @ApiResource(attributes={ - * "normalization_context"={"groups"={"custom_attr_dummy_read"}}, - * "denormalization_context"={"groups"={"custom_attr_dummy_write"}} - * }, - * itemOperations={ - * "get"={"method"="GET", - * "normalization_context"={"groups"={"custom_attr_dummy_get"}}, - * "denormalization_context"={"groups"={"custom_attr_dummy_get"}} - * }, - * "put"={"method"="PUT", - * "normalization_context"={"groups"={"custom_attr_dummy_put"}}, - * "denormalization_context"={"groups"={"custom_attr_dummy_put"}} + * @ApiResource( + * attributes={ + * "normalization_context"={"groups"={"overrode_operation_dummy_read"}}, + * "denormalization_context"={"groups"={"overrode_operation_dummy_write"}} * }, + * itemOperations={ + * "get"={ + * "method"="GET", + * "normalization_context"={"groups"={"overrode_operation_dummy_get"}}, + * "denormalization_context"={"groups"={"overrode_operation_dummy_get"}} + * }, + * "put"={ + * "method"="PUT", + * "normalization_context"={"groups"={"overrode_operation_dummy_put"}}, + * "denormalization_context"={"groups"={"overrode_operation_dummy_put"}} + * }, * "delete"={"method"="DELETE"} - * }, + * } * ) * @ORM\Entity + * + * @author Amrouche Hamza */ -class CustomAttributeDummy +class OverrodeOperationDummy { /** * @var int The id. @@ -56,7 +59,7 @@ class CustomAttributeDummy * * @ORM\Column * @Assert\NotBlank - * @Groups({"custom_attr_dummy_read", "custom_attr_dummy_write", "custom_attr_dummy_get"}) + * @Groups({"overrode_operation_dummy_read", "overrode_operation_dummy_write", "overrode_operation_dummy_get"}) * @ApiProperty(iri="http://schema.org/name") */ private $name; @@ -65,7 +68,7 @@ class CustomAttributeDummy * @var string The dummy name alias. * * @ORM\Column(nullable=true) - * @Groups({"custom_attr_dummy_read", "custom_attr_dummy_put", "custom_attr_dummy_get"}) + * @Groups({"overrode_operation_dummy_read", "overrode_operation_dummy_put", "overrode_operation_dummy_get"}) * @ApiProperty(iri="https://schema.org/alternateName") */ private $alias; @@ -74,17 +77,14 @@ class CustomAttributeDummy * @var string A short description of the item. * * @ORM\Column(nullable=true) - * @Groups({"custom_attr_dummy_read" ,"custom_attr_dummy_write", "custom_attr_dummy_get", "custom_attr_dummy_put"}) + * @Groups({"overrode_operation_dummy_read" ,"overrode_operation_dummy_write", "overrode_operation_dummy_get", "overrode_operation_dummy_put"}) * @ApiProperty(iri="https://schema.org/description") */ public $description; /** - * @var string A short description of the item. - * * @ORM\Column(nullable=true) - * @Groups({"custom_attr_dummy_write"}) - * @ApiProperty(iri="https://schema.org/description") + * @Groups({"overrode_operation_dummy_write"}) */ public $notGettable; From d03c6290e0efabfe2579bfaacaff585782f4b4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 4 Jul 2016 10:38:28 +0200 Subject: [PATCH 07/10] Fix test CS --- tests/Fixtures/TestBundle/Entity/AbstractDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/CompositeItem.php | 2 ++ tests/Fixtures/TestBundle/Entity/CompositeLabel.php | 4 +++- tests/Fixtures/TestBundle/Entity/CompositeRelation.php | 4 +++- tests/Fixtures/TestBundle/Entity/ConcreteDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/CustomIdentifierDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php | 2 +- .../TestBundle/Entity/CustomWritableIdentifierDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/DummyFriend.php | 4 ++-- tests/Fixtures/TestBundle/Entity/FileConfigDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/NoCollectionDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php | 4 ++-- tests/Fixtures/TestBundle/Entity/ParentDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/RelatedDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/RelationEmbedder.php | 2 +- tests/Fixtures/TestBundle/Entity/SingleFileConfigDummy.php | 2 +- tests/Fixtures/TestBundle/Entity/ThirdLevel.php | 2 +- tests/Fixtures/TestBundle/Entity/User.php | 2 ++ 18 files changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/Fixtures/TestBundle/Entity/AbstractDummy.php b/tests/Fixtures/TestBundle/Entity/AbstractDummy.php index 9c975e83d6d..0799318e01b 100644 --- a/tests/Fixtures/TestBundle/Entity/AbstractDummy.php +++ b/tests/Fixtures/TestBundle/Entity/AbstractDummy.php @@ -18,7 +18,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * AbstractDummy. + * Abstract Dummy. * * @author Jérémy Derussé * diff --git a/tests/Fixtures/TestBundle/Entity/CompositeItem.php b/tests/Fixtures/TestBundle/Entity/CompositeItem.php index 7a7eba082b4..29531207fbd 100644 --- a/tests/Fixtures/TestBundle/Entity/CompositeItem.php +++ b/tests/Fixtures/TestBundle/Entity/CompositeItem.php @@ -16,6 +16,8 @@ use Symfony\Component\Serializer\Annotation\Groups; /** + * Composite Item. + * * @ApiResource * @ORM\Entity */ diff --git a/tests/Fixtures/TestBundle/Entity/CompositeLabel.php b/tests/Fixtures/TestBundle/Entity/CompositeLabel.php index 557fa3d5b81..d126743afca 100644 --- a/tests/Fixtures/TestBundle/Entity/CompositeLabel.php +++ b/tests/Fixtures/TestBundle/Entity/CompositeLabel.php @@ -16,8 +16,10 @@ use Symfony\Component\Serializer\Annotation\Groups; /** - * @ORM\Entity + * Composite Label. + * * @ApiResource + * @ORM\Entity */ class CompositeLabel { diff --git a/tests/Fixtures/TestBundle/Entity/CompositeRelation.php b/tests/Fixtures/TestBundle/Entity/CompositeRelation.php index feb8578bb9f..26cb1348a99 100644 --- a/tests/Fixtures/TestBundle/Entity/CompositeRelation.php +++ b/tests/Fixtures/TestBundle/Entity/CompositeRelation.php @@ -16,8 +16,10 @@ use Symfony\Component\Serializer\Annotation\Groups; /** - * @ORM\Entity + * Composite Relation. + * * @ApiResource + * @ORM\Entity */ class CompositeRelation { diff --git a/tests/Fixtures/TestBundle/Entity/ConcreteDummy.php b/tests/Fixtures/TestBundle/Entity/ConcreteDummy.php index 422ed50705f..d640cb72bdd 100644 --- a/tests/Fixtures/TestBundle/Entity/ConcreteDummy.php +++ b/tests/Fixtures/TestBundle/Entity/ConcreteDummy.php @@ -16,7 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * ConcreteDummy. + * Concrete Dummy. * * @author Jérémy Derusse * diff --git a/tests/Fixtures/TestBundle/Entity/CustomIdentifierDummy.php b/tests/Fixtures/TestBundle/Entity/CustomIdentifierDummy.php index a6af6dd6e33..36f981dff70 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomIdentifierDummy.php +++ b/tests/Fixtures/TestBundle/Entity/CustomIdentifierDummy.php @@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * Custom identifier dummy. + * Custom Identifier Dummy. * * @ApiResource * @ORM\Entity diff --git a/tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php b/tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php index df508b049fc..dd72f8cc042 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php @@ -18,7 +18,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * Custom normalized dummy. + * Custom Normalized Dummy. * * @author Mikaël Labrut * diff --git a/tests/Fixtures/TestBundle/Entity/CustomWritableIdentifierDummy.php b/tests/Fixtures/TestBundle/Entity/CustomWritableIdentifierDummy.php index 8df16eb22cf..1185f0bc9d8 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomWritableIdentifierDummy.php +++ b/tests/Fixtures/TestBundle/Entity/CustomWritableIdentifierDummy.php @@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * Custom writable identifier dummy. + * Custom Writable Identifier Dummy. * * @ApiResource * @ORM\Entity diff --git a/tests/Fixtures/TestBundle/Entity/DummyFriend.php b/tests/Fixtures/TestBundle/Entity/DummyFriend.php index 6bfe36e0c5b..901929911c7 100644 --- a/tests/Fixtures/TestBundle/Entity/DummyFriend.php +++ b/tests/Fixtures/TestBundle/Entity/DummyFriend.php @@ -18,11 +18,11 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * DummyFriend. + * Dummy Friend. * * @author Kévin Dunglas * - * @ApiResource() + * @ApiResource * @ORM\Entity */ class DummyFriend diff --git a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php index fb401e952f5..f6f7b876914 100644 --- a/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php +++ b/tests/Fixtures/TestBundle/Entity/FileConfigDummy.php @@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * FileConfigDummy. + * File Config Dummy. * * @ORM\Entity */ diff --git a/tests/Fixtures/TestBundle/Entity/NoCollectionDummy.php b/tests/Fixtures/TestBundle/Entity/NoCollectionDummy.php index b1dc6c69b23..1c96c9016d0 100644 --- a/tests/Fixtures/TestBundle/Entity/NoCollectionDummy.php +++ b/tests/Fixtures/TestBundle/Entity/NoCollectionDummy.php @@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * No collection dummy. + * No Collection Dummy. * * @ApiResource(collectionOperations={}) * @ORM\Entity diff --git a/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php b/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php index c8858cb3ae2..d6cc1dd8462 100644 --- a/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php +++ b/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php @@ -20,6 +20,8 @@ /** * Overrode Operation Dummy. * + * @author Amrouche Hamza + * * @ApiResource( * attributes={ * "normalization_context"={"groups"={"overrode_operation_dummy_read"}}, @@ -40,8 +42,6 @@ * } * ) * @ORM\Entity - * - * @author Amrouche Hamza */ class OverrodeOperationDummy { diff --git a/tests/Fixtures/TestBundle/Entity/ParentDummy.php b/tests/Fixtures/TestBundle/Entity/ParentDummy.php index c52aa5cff8b..5ebcceee66a 100644 --- a/tests/Fixtures/TestBundle/Entity/ParentDummy.php +++ b/tests/Fixtures/TestBundle/Entity/ParentDummy.php @@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * Parent dummy. + * Parent Dummy. * * @author Kévin Dunglas * diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php index ff1789dd668..ea42412c2da 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php @@ -17,7 +17,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * Related dummy. + * Related Dummy. * * @author Kévin Dunglas * diff --git a/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php b/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php index 54283f860a2..21d89825ea6 100644 --- a/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php +++ b/tests/Fixtures/TestBundle/Entity/RelationEmbedder.php @@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Annotation\Groups; /** - * Relation embedder. + * Relation Embedder. * * @author Kévin Dunglas * diff --git a/tests/Fixtures/TestBundle/Entity/SingleFileConfigDummy.php b/tests/Fixtures/TestBundle/Entity/SingleFileConfigDummy.php index 72ee7b9b336..e2ab788122b 100644 --- a/tests/Fixtures/TestBundle/Entity/SingleFileConfigDummy.php +++ b/tests/Fixtures/TestBundle/Entity/SingleFileConfigDummy.php @@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * FileConfigDummy. + * File Config Dummy. * * @ORM\Entity */ diff --git a/tests/Fixtures/TestBundle/Entity/ThirdLevel.php b/tests/Fixtures/TestBundle/Entity/ThirdLevel.php index ace5e49a6de..da5a6d70fbf 100644 --- a/tests/Fixtures/TestBundle/Entity/ThirdLevel.php +++ b/tests/Fixtures/TestBundle/Entity/ThirdLevel.php @@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Annotation\Groups; /** - * ThirdLevel. + * Third Level. * * @author Kévin Dunglas * diff --git a/tests/Fixtures/TestBundle/Entity/User.php b/tests/Fixtures/TestBundle/Entity/User.php index 9c9faedfb4d..14b4512991e 100644 --- a/tests/Fixtures/TestBundle/Entity/User.php +++ b/tests/Fixtures/TestBundle/Entity/User.php @@ -18,6 +18,8 @@ use Symfony\Component\Serializer\Annotation\Groups; /** + * A User. + * * @ORM\Entity * @ApiResource(attributes={ * "normalization_context"={"groups"={"user", "user-read"}}, From 9d67c786fcda61dcb8a585f34d680f981b592464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 4 Jul 2016 10:42:48 +0200 Subject: [PATCH 08/10] Test that overriden operations are supported by content negotiation --- ...n.feature => overridden_operation.feature} | 71 +++++++++++-------- ...Dummy.php => OverriddenOperationDummy.php} | 24 +++---- 2 files changed, 53 insertions(+), 42 deletions(-) rename features/{overriding_operation.feature => overridden_operation.feature} (51%) rename tests/Fixtures/TestBundle/Entity/{OverrodeOperationDummy.php => OverriddenOperationDummy.php} (68%) diff --git a/features/overriding_operation.feature b/features/overridden_operation.feature similarity index 51% rename from features/overriding_operation.feature rename to features/overridden_operation.feature index 9b5155d7629..a19ededcdf1 100644 --- a/features/overriding_operation.feature +++ b/features/overridden_operation.feature @@ -1,14 +1,14 @@ -Feature: Create-Retrieve-Update-Delete with a Overrode Operation context +Feature: Create-Retrieve-Update-Delete with a Overridden Operation context In order to use an hypermedia API As a client software developer I need to be able to retrieve, create, update and delete JSON-LD encoded resources. @createSchema Scenario: Create a resource - When I send a "POST" request to "/overrode_operation_dummies" with body: + When I send a "POST" request to "/overridden_operation_dummies" with body: """ { - "name": "My Overrode Operation Dummy", + "name": "My Overridden Operation Dummy", "description" : "Gerard", "alias": "notWritable" } @@ -19,52 +19,63 @@ Feature: Create-Retrieve-Update-Delete with a Overrode Operation context And the JSON should be equal to: """ { - "@context": "/contexts/OverrodeOperationDummy", - "@id": "/overrode_operation_dummies/1", - "@type": "OverrodeOperationDummy", - "name": "My Overrode Operation Dummy", + "@context": "/contexts/OverriddenOperationDummy", + "@id": "/overridden_operation_dummies/1", + "@type": "OverriddenOperationDummy", + "name": "My Overridden Operation Dummy", "alias": null, "description": "Gerard" } """ Scenario: Get a resource - When I send a "GET" request to "/overrode_operation_dummies/1" + When I send a "GET" request to "/overridden_operation_dummies/1" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/OverrodeOperationDummy", - "@id": "/overrode_operation_dummies/1", - "@type": "OverrodeOperationDummy", - "name": "My Overrode Operation Dummy", + "@context": "/contexts/OverriddenOperationDummy", + "@id": "/overridden_operation_dummies/1", + "@type": "OverriddenOperationDummy", + "name": "My Overridden Operation Dummy", "alias": null, "description": "Gerard" } """ + Scenario: Get a resource in XML + When I add "Accept" header equal to "application/xml" + And I send a "GET" request to "/overridden_operation_dummies/1" + Then the response status code should be 200 + And the header "Content-Type" should be equal to "application/xml" + And the response should be equal to + """ + +My Overridden Operation DummyGerard + """ + Scenario: Get a not found exception - When I send a "GET" request to "/overrode_operation_dummies/42" + When I send a "GET" request to "/overridden_operation_dummies/42" Then the response status code should be 404 Scenario: Get a collection - When I send a "GET" request to "/overrode_operation_dummies" + When I send a "GET" request to "/overridden_operation_dummies" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/OverrodeOperationDummy", - "@id": "\/overrode_operation_dummies", + "@context": "/contexts/OverriddenOperationDummy", + "@id": "\/overridden_operation_dummies", "@type": "hydra:Collection", "hydra:member": [ { - "@id": "\/overrode_operation_dummies\/1", - "@type": "OverrodeOperationDummy", - "name": "My Overrode Operation Dummy", + "@id": "\/overridden_operation_dummies\/1", + "@type": "OverriddenOperationDummy", + "name": "My Overridden Operation Dummy", "alias": null, "description": "Gerard" } @@ -74,10 +85,10 @@ Feature: Create-Retrieve-Update-Delete with a Overrode Operation context """ Scenario: Update a resource - When I send a "PUT" request to "/overrode_operation_dummies/1" with body: + When I send a "PUT" request to "/overridden_operation_dummies/1" with body: """ { - "@id": "/overrode_operation_dummies/1", + "@id": "/overridden_operation_dummies/1", "name": "A nice dummy", "alias": "Dummy" } @@ -88,26 +99,26 @@ Feature: Create-Retrieve-Update-Delete with a Overrode Operation context And the JSON should be equal to: """ { - "@context": "/contexts/OverrodeOperationDummy", - "@id": "/overrode_operation_dummies/1", - "@type": "OverrodeOperationDummy", + "@context": "/contexts/OverriddenOperationDummy", + "@id": "/overridden_operation_dummies/1", + "@type": "OverriddenOperationDummy", "alias": "Dummy", "description": "Gerard" } """ Scenario: Get the final resource - When I send a "GET" request to "/overrode_operation_dummies/1" + When I send a "GET" request to "/overridden_operation_dummies/1" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { - "@context": "/contexts/OverrodeOperationDummy", - "@id": "/overrode_operation_dummies/1", - "@type": "OverrodeOperationDummy", - "name": "My Overrode Operation Dummy", + "@context": "/contexts/OverriddenOperationDummy", + "@id": "/overridden_operation_dummies/1", + "@type": "OverriddenOperationDummy", + "name": "My Overridden Operation Dummy", "alias": "Dummy", "description": "Gerard" } @@ -115,6 +126,6 @@ Feature: Create-Retrieve-Update-Delete with a Overrode Operation context @dropSchema Scenario: Delete a resource - When I send a "DELETE" request to "/overrode_operation_dummies/1" + When I send a "DELETE" request to "/overridden_operation_dummies/1" Then the response status code should be 204 And the response should be empty diff --git a/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php b/tests/Fixtures/TestBundle/Entity/OverriddenOperationDummy.php similarity index 68% rename from tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php rename to tests/Fixtures/TestBundle/Entity/OverriddenOperationDummy.php index d6cc1dd8462..890bba60a5b 100644 --- a/tests/Fixtures/TestBundle/Entity/OverrodeOperationDummy.php +++ b/tests/Fixtures/TestBundle/Entity/OverriddenOperationDummy.php @@ -18,32 +18,32 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * Overrode Operation Dummy. + * Overridden Operation Dummy. * * @author Amrouche Hamza * * @ApiResource( * attributes={ - * "normalization_context"={"groups"={"overrode_operation_dummy_read"}}, - * "denormalization_context"={"groups"={"overrode_operation_dummy_write"}} + * "normalization_context"={"groups"={"overridden_operation_dummy_read"}}, + * "denormalization_context"={"groups"={"overridden_operation_dummy_write"}} * }, * itemOperations={ * "get"={ * "method"="GET", - * "normalization_context"={"groups"={"overrode_operation_dummy_get"}}, - * "denormalization_context"={"groups"={"overrode_operation_dummy_get"}} + * "normalization_context"={"groups"={"overridden_operation_dummy_get"}}, + * "denormalization_context"={"groups"={"overridden_operation_dummy_get"}} * }, * "put"={ * "method"="PUT", - * "normalization_context"={"groups"={"overrode_operation_dummy_put"}}, - * "denormalization_context"={"groups"={"overrode_operation_dummy_put"}} + * "normalization_context"={"groups"={"overridden_operation_dummy_put"}}, + * "denormalization_context"={"groups"={"overridden_operation_dummy_put"}} * }, * "delete"={"method"="DELETE"} * } * ) * @ORM\Entity */ -class OverrodeOperationDummy +class OverriddenOperationDummy { /** * @var int The id. @@ -59,7 +59,7 @@ class OverrodeOperationDummy * * @ORM\Column * @Assert\NotBlank - * @Groups({"overrode_operation_dummy_read", "overrode_operation_dummy_write", "overrode_operation_dummy_get"}) + * @Groups({"overridden_operation_dummy_read", "overridden_operation_dummy_write", "overridden_operation_dummy_get"}) * @ApiProperty(iri="http://schema.org/name") */ private $name; @@ -68,7 +68,7 @@ class OverrodeOperationDummy * @var string The dummy name alias. * * @ORM\Column(nullable=true) - * @Groups({"overrode_operation_dummy_read", "overrode_operation_dummy_put", "overrode_operation_dummy_get"}) + * @Groups({"overridden_operation_dummy_read", "overridden_operation_dummy_put", "overridden_operation_dummy_get"}) * @ApiProperty(iri="https://schema.org/alternateName") */ private $alias; @@ -77,14 +77,14 @@ class OverrodeOperationDummy * @var string A short description of the item. * * @ORM\Column(nullable=true) - * @Groups({"overrode_operation_dummy_read" ,"overrode_operation_dummy_write", "overrode_operation_dummy_get", "overrode_operation_dummy_put"}) + * @Groups({"overridden_operation_dummy_read" ,"overridden_operation_dummy_write", "overridden_operation_dummy_get", "overridden_operation_dummy_put"}) * @ApiProperty(iri="https://schema.org/description") */ public $description; /** * @ORM\Column(nullable=true) - * @Groups({"overrode_operation_dummy_write"}) + * @Groups({"overridden_operation_dummy_write"}) */ public $notGettable; From 2bc175447dcaf15fa41b63ce83b6a4e3ab4c0db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 4 Jul 2016 17:41:17 +0200 Subject: [PATCH 09/10] Refactor the Serializer context building process --- features/content_negotiation.feature | 40 +++++++++- src/Api/RequestAttributesExtractor.php | 6 +- .../Symfony/Bundle/Resources/config/api.xml | 10 ++- .../Symfony/Bundle/Resources/config/hydra.xml | 1 - .../DeserializerViewListener.php | 41 +++++----- src/EventListener/SerializerViewListener.php | 34 +++----- .../CollectionFiltersNormalizer.php | 2 +- src/Hydra/Serializer/CollectionNormalizer.php | 10 +-- src/JsonLd/Serializer/ContextTrait.php | 74 +++--------------- src/JsonLd/Serializer/ItemNormalizer.php | 30 +++++-- src/Serializer/SerializerContextBuilder.php | 59 ++++++++++++++ .../SerializerContextBuilderInterface.php | 36 +++++++++ .../ApiPlatformExtensionTest.php | 2 + .../DeserializerViewListenerTest.php | 33 +++++--- .../SerializerViewListenerTest.php | 31 ++++++-- tests/Fixtures/app/config/config.yml | 1 + .../SerializerContextBuilderTest.php | 78 +++++++++++++++++++ 17 files changed, 342 insertions(+), 146 deletions(-) create mode 100644 src/Serializer/SerializerContextBuilder.php create mode 100644 src/Serializer/SerializerContextBuilderInterface.php create mode 100644 tests/Serializer/SerializerContextBuilderTest.php diff --git a/features/content_negotiation.feature b/features/content_negotiation.feature index 84ffc8e1f7f..a174931249c 100644 --- a/features/content_negotiation.feature +++ b/features/content_negotiation.feature @@ -12,13 +12,51 @@ Feature: Content Negotiation support XML! """ - Then the header "Content-Type" should be equal to "application/xml" + Then the response status code should be 201 + And the header "Content-Type" should be equal to "application/xml" And the response should be equal to """ 1XML! """ + Scenario: Retrieve a collection in XML + When I add "Accept" header equal to "text/xml" + And I send a "GET" request to "/dummies" + Then the response status code should be 200 + And the header "Content-Type" should be equal to "text/xml; charset=UTF-8" + And the response should be equal to + """ + +1XML! + """ + + Scenario: Retrieve a collection in JSON + When I add "Accept" header equal to "application/json" + And I send a "GET" request to "/dummies" + Then the response status code should be 200 + And the header "Content-Type" should be equal to "application/json" + And the response should be in JSON + And the JSON should be equal to: + """ +[ + { + "id": 1, + "name": "XML!", + "alias": null, + "description": null, + "dummyDate": null, + "dummyPrice": null, + "jsonData": [], + "relatedDummy": null, + "dummyBoolean": null, + "dummy": null, + "relatedDummies": [], + "nameConverted": null + } +] + """ + @dropSchema Scenario: Requesting an unknown format should return JSON-LD When I add "Accept" header equal to "text/plain" diff --git a/src/Api/RequestAttributesExtractor.php b/src/Api/RequestAttributesExtractor.php index 6027a2289a5..fb7c3d54c7e 100644 --- a/src/Api/RequestAttributesExtractor.php +++ b/src/Api/RequestAttributesExtractor.php @@ -18,6 +18,8 @@ * Extracts data used by the library form a Request instance. * * @author Kévin Dunglas + * + * @internal */ final class RequestAttributesExtractor { @@ -29,19 +31,17 @@ final class RequestAttributesExtractor * * @throws RuntimeException * - * @return array + * @return array [$resourceClass, $collectionOperation, $itemOperation, $format] */ public static function extractAttributes(Request $request) { $resourceClass = $request->attributes->get('_resource_class'); - if (!$resourceClass) { throw new RuntimeException('The request attribute "_resource_class" must be defined.'); } $collectionOperation = $request->attributes->get('_collection_operation_name'); $itemOperation = $request->attributes->get('_item_operation_name'); - if (!$itemOperation && !$collectionOperation) { throw new RuntimeException('One of the request attribute "_item_operation_name" or "_collection_operation_name" must be defined.'); } diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 702f20df78b..6d90d0d8070 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -43,6 +43,12 @@ + + + + + + @@ -60,14 +66,14 @@ - + - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml index 2e951c3e0b8..77cd32bbeae 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml @@ -44,7 +44,6 @@ - diff --git a/src/EventListener/DeserializerViewListener.php b/src/EventListener/DeserializerViewListener.php index a6902c65207..f56e8f92a41 100644 --- a/src/EventListener/DeserializerViewListener.php +++ b/src/EventListener/DeserializerViewListener.php @@ -13,7 +13,7 @@ use ApiPlatform\Core\Api\RequestAttributesExtractor; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; @@ -27,46 +27,41 @@ final class DeserializerViewListener { private $serializer; - private $resourceMetadataFactory; + private $serializerContextBuilder; - public function __construct(SerializerInterface $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder) { $this->serializer = $serializer; - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->serializerContextBuilder = $serializerContextBuilder; } + /** + * Deserializes the data sent in the requested format. + * + * @param GetResponseForControllerResultEvent $event + */ public function onKernelView(GetResponseForControllerResultEvent $event) { - $data = $event->getControllerResult(); + $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); - if ($data instanceof Response || !in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT], true)) { + if ($controllerResult instanceof Response || !in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT], true)) { return; } try { - list($resourceClass, $collectionOperationName, $itemOperationName, $format) = RequestAttributesExtractor::extractAttributes($request); + $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if ($collectionOperationName) { - $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, 'denormalization_context', [], true); - $context['collection_operation_name'] = $collectionOperationName; - } else { - $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, 'denormalization_context', [], true); - $context['item_operation_name'] = $itemOperationName; - } - - $context['resource_class'] = $resourceClass; - $context['request_uri'] = $request->getRequestUri(); - - if (null !== $data) { - $context['object_to_populate'] = $data; + $context = $this->serializerContextBuilder->createFromRequest($request, false, $extractedAttributes); + if (null !== $controllerResult) { + $context['object_to_populate'] = $controllerResult; } - $event->setControllerResult($this->serializer->deserialize($request->getContent(), $resourceClass, $format, $context)); + $event->setControllerResult( + $this->serializer->deserialize($request->getContent(), $extractedAttributes[0], $extractedAttributes[3], $context) + ); } } diff --git a/src/EventListener/SerializerViewListener.php b/src/EventListener/SerializerViewListener.php index 5b620646b85..c398b48779b 100644 --- a/src/EventListener/SerializerViewListener.php +++ b/src/EventListener/SerializerViewListener.php @@ -12,7 +12,8 @@ namespace ApiPlatform\Core\EventListener; use ApiPlatform\Core\Api\RequestAttributesExtractor; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\Serializer\SerializerInterface; @@ -21,49 +22,38 @@ * * @author Kévin Dunglas */ -class SerializerViewListener +final class SerializerViewListener { private $serializer; - private $resourceMetadataFactory; + private $serializerContextBuilder; - public function __construct(SerializerInterface $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder) { $this->serializer = $serializer; - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->serializerContextBuilder = $serializerContextBuilder; } /** * Serializes the data to the requested format. + * + * @param GetResponseForControllerResultEvent $event */ public function onKernelView(GetResponseForControllerResultEvent $event) { $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); - $format = $request->attributes->get('_api_format'); - if ($controllerResult instanceof Response || !$format) { + if ($controllerResult instanceof Response) { return; } try { - list($resourceClass, $collectionOperationName, $itemOperationName, $format) = RequestAttributesExtractor::extractAttributes($request); + $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if ($collectionOperationName) { - $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, 'normalization_context', [], true); - $context['collection_operation_name'] = $collectionOperationName; - } else { - $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, 'normalization_context', [], true); - $context['item_operation_name'] = $itemOperationName; - } - - $context['resource_class'] = $resourceClass; - $context['request_uri'] = $request->getRequestUri(); - - $event->setControllerResult($this->serializer->serialize($controllerResult, $format, $context)); + $context = $this->serializerContextBuilder->createFromRequest($request, true, $extractedAttributes); + $event->setControllerResult($this->serializer->serialize($controllerResult, $extractedAttributes[3], $context)); } } diff --git a/src/Hydra/Serializer/CollectionFiltersNormalizer.php b/src/Hydra/Serializer/CollectionFiltersNormalizer.php index e57ba25f2cf..d2cf0696542 100644 --- a/src/Hydra/Serializer/CollectionFiltersNormalizer.php +++ b/src/Hydra/Serializer/CollectionFiltersNormalizer.php @@ -64,7 +64,7 @@ public function normalize($object, $format = null, array $context = []) return $data; } - $resourceClass = $this->getResourceClass($this->resourceClassResolver, $object, $context); + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true); $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); $operationName = $context['collection_operation_name'] ?? null; diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php index 5104997330d..17356e36c93 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -17,7 +17,6 @@ use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\JsonLd\ContextBuilderInterface; use ApiPlatform\Core\JsonLd\Serializer\ContextTrait; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerAwareTrait; @@ -35,14 +34,12 @@ final class CollectionNormalizer implements NormalizerInterface, SerializerAware const FORMAT = 'jsonld'; - private $resourceMetadataFactory; private $contextBuilder; private $resourceClassResolver; private $iriConverter; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, IriConverterInterface $iriConverter) + public function __construct(ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, IriConverterInterface $iriConverter) { - $this->resourceMetadataFactory = $resourceMetadataFactory; $this->contextBuilder = $contextBuilder; $this->resourceClassResolver = $resourceClassResolver; $this->iriConverter = $iriConverter; @@ -78,10 +75,9 @@ public function normalize($object, $format = null, array $context = []) return $data; } - $resourceClass = $this->getResourceClass($this->resourceClassResolver, $object, $context); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true); $data = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); - $context = $this->createContext($resourceClass, $resourceMetadata, $context, true); + $context = $this->createContext($resourceClass, $context); $data['@id'] = $this->iriConverter->getIriFromResourceClass($resourceClass); $data['@type'] = 'hydra:Collection'; diff --git a/src/JsonLd/Serializer/ContextTrait.php b/src/JsonLd/Serializer/ContextTrait.php index 3d72a5134ef..0a96acf6bc4 100644 --- a/src/JsonLd/Serializer/ContextTrait.php +++ b/src/JsonLd/Serializer/ContextTrait.php @@ -11,101 +11,49 @@ namespace ApiPlatform\Core\JsonLd\Serializer; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\JsonLd\ContextBuilderInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; /** * Creates and manipulates the Serializer context. * * @author Kévin Dunglas + * + * @internal */ trait ContextTrait { /** * Import the context defined in metadata and set some default values. * - * @param string $resourceClass - * @param ResourceMetadata $resourceMetadata - * @param array $context + * @param string $resourceClass + * @param array $context * * @return array */ - private function createContext(string $resourceClass, ResourceMetadata $resourceMetadata, array $context, bool $normalization) : array + private function createContext(string $resourceClass, array $context) : array { if (isset($context['jsonld_has_context'])) { return $context; } - $key = $normalization ? 'normalization_context' : 'denormalization_context'; - $context = array_merge($context, $this->getContextValue($resourceMetadata, $context, $key, [])); - $context['resource_class'] = $resourceClass; - return array_merge($context, [ 'jsonld_has_context' => true, // Don't use hydra:Collection in sub levels 'jsonld_sub_level' => true, + 'resource_class' => $resourceClass, ]); } /** - * Updates the resource class and remove the object_to_populate key. + * Updates the given JSON-LD document to add its @context key. * - * @param string $resourceClass - * @param array $context + * @param ContextBuilderInterface $contextBuilder + * @param string $resourceClass + * @param array $context + * @param array $data * * @return array */ - private function createRelationContext(string $resourceClass, array $context) : array - { - $context['resource_class'] = $resourceClass; - unset($context['item_operation_name']); - unset($context['collection_operation_name']); - - return $context; - } - - /** - * Gets a context value. - * - * @param ResourceMetadata $resourceMetadata - * @param array $context - * @param string $key - * @param mixed $defaultValue - * - * @return mixed - */ - private function getContextValue(ResourceMetadata $resourceMetadata, array $context, string $key, $defaultValue = null) - { - if (isset($context[$key])) { - return $context[$key]; - } - - if (isset($context['collection_operation_name'])) { - return $resourceMetadata->getCollectionOperationAttribute($context['collection_operation_name'], $key, $defaultValue, true); - } - - if (isset($context['item_operation_name'])) { - return $resourceMetadata->getItemOperationAttribute($context['item_operation_name'], $key, $defaultValue, true); - } - - return $resourceMetadata->getAttribute($key, $defaultValue); - } - - /** - * Gets the resource class to use depending of the current data and context. - * - * @param ResourceClassResolverInterface $resourceClassResolver - * @param mixed $data - * @param array $context - * - * @return string - */ - private function getResourceClass(ResourceClassResolverInterface $resourceClassResolver, $data, array $context) : string - { - return $resourceClassResolver->getResourceClass($data, $context['resource_class'] ?? null, true); - } - private function addJsonLdContext(ContextBuilderInterface $contextBuilder, string $resourceClass, array $context, array $data = []) : array { if (isset($context['jsonld_has_context'])) { diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index fef644f5006..801f51f4c9e 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -84,13 +84,13 @@ public function supportsNormalization($data, $format = null) */ public function normalize($object, $format = null, array $context = []) { - $resourceClass = $this->getResourceClass($this->resourceClassResolver, $object, $context); + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true); $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); $data = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); $context['jsonld_normalize'] = true; - $context = $this->createContext($resourceClass, $resourceMetadata, $context, true); + $context = $this->createContext($resourceClass, $context); $rawData = parent::normalize($object, $format, $context); if (!is_array($rawData)) { @@ -116,11 +116,10 @@ public function supportsDenormalization($data, $type, $format = null) */ public function denormalize($data, $class, $format = null, array $context = []) { - $resourceClass = $this->getResourceClass($this->resourceClassResolver, $data, $context); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + $resourceClass = $this->resourceClassResolver->getResourceClass($data, $context['resource_class']); $context['jsonld_denormalize'] = true; - $context = $this->createContext($resourceClass, $resourceMetadata, $context, false); + $context = $this->createContext($resourceClass, $context); // Avoid issues with proxies if we populated the object $overrideClass = isset($data['@id']) && !isset($context['object_to_populate']); @@ -272,7 +271,7 @@ protected function setAttributeValue($object, $attribute, $value, $format = null private function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, array $context) { if ($propertyMetadata->isReadableLink()) { - return $this->serializer->normalize($relatedObject, self::FORMAT, $this->createRelationContext($resourceClass, $context)); + return $this->serializer->normalize($relatedObject, self::FORMAT, $this->createRelationSerializationContext($resourceClass, $context)); } return $this->iriConverter->getIriFromItem($relatedObject); @@ -303,7 +302,7 @@ private function denormalizeRelation(string $resourceClass, string $attributeNam } if (!$this->resourceClassResolver->isResourceClass($className) || $propertyMetadata->isWritableLink()) { - return $this->serializer->denormalize($value, $className, self::FORMAT, $this->createRelationContext($className, $context)); + return $this->serializer->denormalize($value, $className, self::FORMAT, $this->createRelationSerializationContext($className, $context)); } if (!is_array($value)) { @@ -364,4 +363,21 @@ private function getFactoryOptions(array $context) : array return $options; } + + /** + * Creates the context to use when serializing a relation. + * + * @param string $resourceClass + * @param array $context + * + * @return array + */ + private function createRelationSerializationContext(string $resourceClass, array $context) : array + { + $context['resource_class'] = $resourceClass; + unset($context['item_operation_name']); + unset($context['collection_operation_name']); + + return $context; + } } diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php new file mode 100644 index 00000000000..ce23fbb9cdd --- /dev/null +++ b/src/Serializer/SerializerContextBuilder.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Serializer; + +use ApiPlatform\Core\Api\RequestAttributesExtractor; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * {@inheritdoc} + * + * @author Kévin Dunglas + */ +final class SerializerContextBuilder implements SerializerContextBuilderInterface +{ + private $resourceMetadataFactory; + + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory) + { + $this->resourceMetadataFactory = $resourceMetadataFactory; + } + + /** + * {@inheritdoc} + */ + public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null) : array + { + if (null === $extractedAttributes) { + $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); + } + + list($resourceClass, $collectionOperationName, $itemOperationName) = $extractedAttributes; + + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + $key = $normalization ? 'normalization_context' : 'denormalization_context'; + + if ($collectionOperationName) { + $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, $key, [], true); + $context['collection_operation_name'] = $collectionOperationName; + } else { + $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, $key, [], true); + $context['item_operation_name'] = $itemOperationName; + } + + $context['resource_class'] = $resourceClass; + $context['request_uri'] = $request->getRequestUri(); + + return $context; + } +} diff --git a/src/Serializer/SerializerContextBuilderInterface.php b/src/Serializer/SerializerContextBuilderInterface.php new file mode 100644 index 00000000000..ea4602f1d1d --- /dev/null +++ b/src/Serializer/SerializerContextBuilderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Serializer; + +use ApiPlatform\Core\Exception\RuntimeException; +use Symfony\Component\HttpFoundation\Request; + +/** + * Builds the context used by the Symfony Serializer. + * + * @author Kévin Dunglas + */ +interface SerializerContextBuilderInterface +{ + /** + * Creates a serialization context from a Request. + * + * @param Request $request + * @param bool $normalization + * @param array|null $extractedAttributes + * + * @throws RuntimeException + * + * @return array + */ + public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null) : array; +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 651b86a8e51..946e9769b41 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -266,6 +266,7 @@ private function getContainerBuilderProphecy() 'api_platform.action.get_collection', 'api_platform.action.post_collection', 'api_platform.action.get_item', + 'api_platform.serializer.context_builder', 'api_platform.doctrine.metadata_factory', 'api_platform.doctrine.orm.collection_data_provider', 'api_platform.doctrine.orm.item_data_provider', @@ -302,6 +303,7 @@ private function getContainerBuilderProphecy() 'api_platform.hydra.action.documentation', 'api_platform.hydra.action.exception', ]; + foreach ($definitions as $definition) { $containerBuilderProphecy->setDefinition($definition, $definitionArgument)->shouldBeCalled(); } diff --git a/tests/EventListener/DeserializerViewListenerTest.php b/tests/EventListener/DeserializerViewListenerTest.php index 03333f8a7d4..1632499e835 100644 --- a/tests/EventListener/DeserializerViewListenerTest.php +++ b/tests/EventListener/DeserializerViewListenerTest.php @@ -12,6 +12,7 @@ namespace ApiPlatform\Core\Tests\EventListener; use ApiPlatform\Core\EventListener\DeserializerViewListener; +use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -36,7 +37,10 @@ public function testDoNotCallWhenAResponse() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->deserialize()->shouldNotBeCalled(); - $listener = new DeserializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new DeserializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -53,7 +57,10 @@ public function testDoNotCallWhenRequestMethodIsSafe() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->deserialize()->shouldNotBeCalled(); - $listener = new DeserializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new DeserializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -70,19 +77,21 @@ public function testDoNotCallWhenRequestNotManaged() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->deserialize()->shouldNotBeCalled(); - $listener = new DeserializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new DeserializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } /** * @dataProvider methodProvider */ - public function testDeserialize($method) + public function testDeserialize(string $method, bool $populateObject) { - $result = new \stdClass(); - + $result = $populateObject ? new \stdClass() : null; $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); - $eventProphecy->getControllerResult()->willReturn(new \stdClass()); + $eventProphecy->getControllerResult()->willReturn($result); $eventProphecy->setControllerResult($result)->shouldBeCalled(); $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'post', '_api_format' => 'json'], [], [], [], '{}'); @@ -90,14 +99,18 @@ public function testDeserialize($method) $eventProphecy->getRequest()->willReturn($request); $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->deserialize('{}', 'Foo', 'json', Argument::type('array'))->willReturn($result); + $context = $populateObject ? ['object_to_populate' => $populateObject] : []; + $serializerProphecy->deserialize('{}', 'Foo', 'json', $context)->willReturn($result); + + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->willReturn([]); - $listener = new DeserializerViewListener($serializerProphecy->reveal()); + $listener = new DeserializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } public function methodProvider() { - return [[Request::METHOD_POST], [Request::METHOD_PUT]]; + return [[Request::METHOD_POST, false], [Request::METHOD_PUT, true]]; } } diff --git a/tests/EventListener/SerializerViewListenerTest.php b/tests/EventListener/SerializerViewListenerTest.php index d3b0d526e1f..f5eced33269 100644 --- a/tests/EventListener/SerializerViewListenerTest.php +++ b/tests/EventListener/SerializerViewListenerTest.php @@ -12,6 +12,7 @@ namespace ApiPlatform\Core\Tests\EventListener; use ApiPlatform\Core\EventListener\SerializerViewListener; +use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -32,7 +33,10 @@ public function testDoNotSerializeResponse() $eventProphecy->getControllerResult()->willReturn(new Response()); $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml'])); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -45,7 +49,10 @@ public function testDoNotSerializeWhenFormatNotSet() $eventProphecy->getControllerResult()->willReturn(new \stdClass()); $eventProphecy->getRequest()->willReturn(new Request()); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -58,7 +65,10 @@ public function testDoNotSerializeWhenResourceClassNotSet() $eventProphecy->getControllerResult()->willReturn(new \stdClass()); $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_collection_operation_name' => 'get'])); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -71,7 +81,10 @@ public function testDoNotSerializeWhenOperationNotSet() $eventProphecy->getControllerResult()->willReturn(new \stdClass()); $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo'])); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -86,7 +99,10 @@ public function testSerializeCollectionOperation() $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_collection_operation_name' => 'get'])); $eventProphecy->setControllerResult('bar')->shouldBeCalled(); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), true, Argument::type('array'))->willReturn($expectedContext); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } @@ -101,7 +117,10 @@ public function testSerializeItemOperation() $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_item_operation_name' => 'get'])); $eventProphecy->setControllerResult('bar')->shouldBeCalled(); - $listener = new SerializerViewListener($serializerProphecy->reveal()); + $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); + $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), true, Argument::type('array'))->willReturn($expectedContext); + + $listener = new SerializerViewListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal()); $listener->onKernelView($eventProphecy->reveal()); } } diff --git a/tests/Fixtures/app/config/config.yml b/tests/Fixtures/app/config/config.yml index a2f1c91c047..173bf393ef4 100644 --- a/tests/Fixtures/app/config/config.yml +++ b/tests/Fixtures/app/config/config.yml @@ -32,6 +32,7 @@ api_platform: formats: jsonld: ['application/ld+json'] xml: ['application/xml', 'text/xml'] + json: ['application/json'] name_converter: 'app.name_converter' enable_fos_user: true collection: diff --git a/tests/Serializer/SerializerContextBuilderTest.php b/tests/Serializer/SerializerContextBuilderTest.php new file mode 100644 index 00000000000..099bf44fb14 --- /dev/null +++ b/tests/Serializer/SerializerContextBuilderTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Serializer; + +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Serializer\SerializerContextBuilder; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Kévin Dunglas + */ +class SerializerContextBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var SerializerContextBuilder + */ + private $builder; + + protected function setUp() + { + $resourceMetadata = new ResourceMetadata( + null, + null, + null, + [], + [], + ['normalization_context' => ['foo' => 'bar'], 'denormalization_context' => ['bar' => 'baz']] + ); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata); + + $this->builder = new SerializerContextBuilder($resourceMetadataFactoryProphecy->reveal()); + } + + public function testCreateFromRequest() + { + $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'xml']); + $expected = ['foo' => 'bar', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; + $this->assertEquals($expected, $this->builder->createFromRequest($request, true)); + + $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'pot', '_api_format' => 'xml']); + $expected = ['foo' => 'bar', 'collection_operation_name' => 'pot', 'resource_class' => 'Foo', 'request_uri' => '']; + $this->assertEquals($expected, $this->builder->createFromRequest($request, true)); + + $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'xml']); + $expected = ['bar' => 'baz', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; + $this->assertEquals($expected, $this->builder->createFromRequest($request, false)); + + $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'post', '_api_format' => 'xml']); + $expected = ['bar' => 'baz', 'collection_operation_name' => 'post', 'resource_class' => 'Foo', 'request_uri' => '']; + $this->assertEquals($expected, $this->builder->createFromRequest($request, false)); + } + + /** + * @expectedException \ApiPlatform\Core\Exception\RuntimeException + */ + public function testThrowExceptionOnInvalidRequest() + { + $this->builder->createFromRequest(new Request(), false); + } + + public function testReuseExistingAttributes() + { + $expected = ['bar' => 'baz', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; + $this->assertEquals($expected, $this->builder->createFromRequest(new Request(), false, ['Foo', null, 'get'])); + } +} From 373af5b7106a6bc77ce7948ee50c341e37262117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 6 Jul 2016 08:16:34 +0200 Subject: [PATCH 10/10] Refactor RequestAttributesExtractor to use explicit keys --- src/Action/GetCollectionAction.php | 4 +- src/Action/GetItemAction.php | 4 +- src/Api/RequestAttributesExtractor.php | 40 ++++++++++++------- .../EventListener/ManagerViewListener.php | 7 ++-- src/Bridge/Symfony/Routing/ApiLoader.php | 7 ++-- src/Bridge/Symfony/Routing/IriConverter.php | 8 ++-- .../Routing/OperationMethodResolver.php | 4 +- .../EventListener/ValidatorViewListener.php | 24 +++++------ .../DeserializerViewListener.php | 6 +-- src/EventListener/FormatRequestListener.php | 2 +- src/EventListener/SerializerViewListener.php | 7 ++-- .../RequestExceptionListener.php | 2 +- src/Serializer/SerializerContextBuilder.php | 22 +++++----- tests/Action/GetCollectionActionTest.php | 2 +- tests/Action/GetItemActionTest.php | 4 +- tests/Api/RequestAttributesExtractorTest.php | 35 +++++++++++----- .../Bridge/Symfony/Routing/ApiLoaderTest.php | 20 +++------- .../Symfony/Routing/IriConverterTest.php | 4 +- .../ValidatorViewListenerTest.php | 6 ++- .../DeserializerViewListenerTest.php | 2 +- .../FormatRequestListenerTest.php | 8 ++-- .../SerializerViewListenerTest.php | 8 ++-- .../Controller/ConfigCustomController.php | 7 +--- .../SerializerContextBuilderTest.php | 10 ++--- 24 files changed, 128 insertions(+), 115 deletions(-) diff --git a/src/Action/GetCollectionAction.php b/src/Action/GetCollectionAction.php index 57f93b8c0b7..3eedae4a9bb 100644 --- a/src/Action/GetCollectionAction.php +++ b/src/Action/GetCollectionAction.php @@ -42,8 +42,8 @@ public function __construct(CollectionDataProviderInterface $collectionDataProvi */ public function __invoke(Request $request) { - list($resourceClass, $operationName) = RequestAttributesExtractor::extractAttributes($request); + $attributes = RequestAttributesExtractor::extractAttributes($request); - return $this->collectionDataProvider->getCollection($resourceClass, $operationName); + return $this->collectionDataProvider->getCollection($attributes['resource_class'], $attributes['collection_operation_name']); } } diff --git a/src/Action/GetItemAction.php b/src/Action/GetItemAction.php index f0a74c1f160..f0083666bec 100644 --- a/src/Action/GetItemAction.php +++ b/src/Action/GetItemAction.php @@ -44,9 +44,9 @@ public function __construct(ItemDataProviderInterface $itemDataProvider) */ public function __invoke(Request $request, $id) { - list($resourceClass, , $operationName) = RequestAttributesExtractor::extractAttributes($request); + $attributes = RequestAttributesExtractor::extractAttributes($request); - $data = $this->itemDataProvider->getItem($resourceClass, $id, $operationName, true); + $data = $this->itemDataProvider->getItem($attributes['resource_class'], $id, $attributes['item_operation_name'], true); if (!$data) { throw new NotFoundHttpException('Not Found'); } diff --git a/src/Api/RequestAttributesExtractor.php b/src/Api/RequestAttributesExtractor.php index fb7c3d54c7e..a5fbd14cc84 100644 --- a/src/Api/RequestAttributesExtractor.php +++ b/src/Api/RequestAttributesExtractor.php @@ -23,34 +23,44 @@ */ final class RequestAttributesExtractor { + const API_ATTRIBUTES = ['resource_class', 'format', 'mime_type']; + /** - * Extracts resource class, operation name and format request attributes. Throws an exception if the request does not contain required - * attributes. + * Extracts resource class, operation name and format request attributes. Throws an exception if the request does not + * contain required attributes. * * @param Request $request * * @throws RuntimeException * - * @return array [$resourceClass, $collectionOperation, $itemOperation, $format] + * @return array */ public static function extractAttributes(Request $request) { - $resourceClass = $request->attributes->get('_resource_class'); - if (!$resourceClass) { - throw new RuntimeException('The request attribute "_resource_class" must be defined.'); - } + $result = []; + + foreach (self::API_ATTRIBUTES as $key) { + $attributeKey = '_api_'.$key; + $attributeValue = $request->attributes->get($attributeKey); - $collectionOperation = $request->attributes->get('_collection_operation_name'); - $itemOperation = $request->attributes->get('_item_operation_name'); - if (!$itemOperation && !$collectionOperation) { - throw new RuntimeException('One of the request attribute "_item_operation_name" or "_collection_operation_name" must be defined.'); + if (null === $attributeValue) { + throw new RuntimeException(sprintf('The request attribute "%s" must be defined.', $attributeKey)); + } + + $result[$key] = $attributeValue; } - $format = $request->attributes->get('_api_format'); - if (!$format) { - throw new RuntimeException('The request attribute "_api_format" must be defined.'); + $collectionOperationName = $request->attributes->get('_api_collection_operation_name'); + $itemOperationName = $request->attributes->get('_api_item_operation_name'); + + if ($collectionOperationName) { + $result['collection_operation_name'] = $collectionOperationName; + } elseif ($itemOperationName) { + $result['item_operation_name'] = $itemOperationName; + } else { + throw new RuntimeException('One of the request attribute "_api_collection_operation_name" or "_api_item_operation_name" must be defined.'); } - return [$resourceClass, $collectionOperation, $itemOperation, $format]; + return $result; } } diff --git a/src/Bridge/Doctrine/EventListener/ManagerViewListener.php b/src/Bridge/Doctrine/EventListener/ManagerViewListener.php index bb84e0b416d..38cc0bae96c 100644 --- a/src/Bridge/Doctrine/EventListener/ManagerViewListener.php +++ b/src/Bridge/Doctrine/EventListener/ManagerViewListener.php @@ -40,17 +40,16 @@ public function __construct(ManagerRegistry $managerRegistry) public function onKernelView(GetResponseForControllerResultEvent $event) { $request = $event->getRequest(); - if (!in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_DELETE])) { + if (!in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_DELETE], true)) { return; } - $resourceClass = $request->attributes->get('_resource_class'); - if (!$resourceClass) { + $resourceClass = $request->attributes->get('_api_resource_class'); + if (null === $resourceClass) { return; } $controllerResult = $event->getControllerResult(); - if (null === $objectManager = $this->getManager($resourceClass, $controllerResult)) { return $controllerResult; } diff --git a/src/Bridge/Symfony/Routing/ApiLoader.php b/src/Bridge/Symfony/Routing/ApiLoader.php index e9cd653db69..180abf17369 100644 --- a/src/Bridge/Symfony/Routing/ApiLoader.php +++ b/src/Bridge/Symfony/Routing/ApiLoader.php @@ -35,7 +35,6 @@ final class ApiLoader extends Loader const ROUTE_NAME_PREFIX = 'api_'; const DEFAULT_ACTION_PATTERN = 'api_platform.action.'; - private $kernel; private $fileLoader; private $resourceNameCollectionFactory; private $resourceMetadataFactory; @@ -44,11 +43,11 @@ final class ApiLoader extends Loader public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathGeneratorInterface $resourcePathGenerator, ContainerInterface $container) { - $this->container = $container; $this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing'))); $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; $this->resourcePathGenerator = $resourcePathGenerator; + $this->container = $container; } /** @@ -144,8 +143,8 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas $path, [ '_controller' => $controller, - '_resource_class' => $resourceClass, - sprintf('_%s_operation_name', $collection ? 'collection' : 'item') => $operationName, + '_api_resource_class' => $resourceClass, + sprintf('_api_%s_operation_name', $collection ? 'collection' : 'item') => $operationName, ], [], [], diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index df84e441b7e..f3495687f8f 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -58,11 +58,11 @@ public function getItemFromIri(string $iri, bool $fetchData = false) throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $exception->getCode(), $exception); } - if (!isset($parameters['_resource_class']) || !isset($parameters['id'])) { + if (!isset($parameters['_api_resource_class']) || !isset($parameters['id'])) { throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); } - if ($item = $this->itemDataProvider->getItem($parameters['_resource_class'], $parameters['id'], null, $fetchData)) { + if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $fetchData)) { return $item; } @@ -175,8 +175,8 @@ private function getRouteName(string $resourceClass, bool $collection) : string $operationType = $collection ? 'collection' : 'item'; foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { - $currentResourceClass = $route->getDefault('_resource_class'); - $operation = $route->getDefault(sprintf('_%s_operation_name', $operationType)); + $currentResourceClass = $route->getDefault('_api_resource_class'); + $operation = $route->getDefault(sprintf('_api_%s_operation_name', $operationType)); $methods = $route->getMethods(); if ($resourceClass === $currentResourceClass && null !== $operation && (empty($methods) || in_array('GET', $methods))) { diff --git a/src/Bridge/Symfony/Routing/OperationMethodResolver.php b/src/Bridge/Symfony/Routing/OperationMethodResolver.php index 7a7c6c46bc9..66a17a658c6 100644 --- a/src/Bridge/Symfony/Routing/OperationMethodResolver.php +++ b/src/Bridge/Symfony/Routing/OperationMethodResolver.php @@ -127,10 +127,10 @@ private function getOperationMethod(string $resourceClass, string $operationName */ private function getOperationRoute(string $resourceClass, string $operationName, bool $collection) : Route { - $operationNameKey = sprintf('_%s_operation_name', $collection ? 'collection' : 'item'); + $operationNameKey = sprintf('_api_%s_operation_name', $collection ? 'collection' : 'item'); foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { - $currentResourceClass = $route->getDefault('_resource_class'); + $currentResourceClass = $route->getDefault('_api_resource_class'); $currentOperationName = $route->getDefault($operationNameKey); if ($resourceClass === $currentResourceClass && $operationName === $currentOperationName) { diff --git a/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php b/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php index 53522740360..aeb05defb77 100644 --- a/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php +++ b/src/Bridge/Symfony/Validator/EventListener/ValidatorViewListener.php @@ -11,7 +11,9 @@ namespace ApiPlatform\Core\Bridge\Symfony\Validator\EventListener; +use ApiPlatform\Core\Api\RequestAttributesExtractor; use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; +use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; @@ -44,26 +46,24 @@ public function onKernelView(GetResponseForControllerResultEvent $event) { $request = $event->getRequest(); - $resourceClass = $request->attributes->get('_resource_class'); - $itemOperationName = $request->attributes->get('_item_operation_name'); - $collectionOperationName = $request->attributes->get('_collection_operation_name'); + try { + $attributes = RequestAttributesExtractor::extractAttributes($request); + } catch (RuntimeException $e) { + return; + } - $method = $request->getMethod(); - if ( - !$resourceClass || (!$itemOperationName && !$collectionOperationName) || - (Request::METHOD_POST !== $method && Request::METHOD_PUT !== $method && Request::METHOD_PATCH !== $method) - ) { + if (!in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH], true)) { return; } $data = $event->getControllerResult(); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - if ($collectionOperationName) { - $validationGroups = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, 'validation_groups'); + if (isset($attributes['collection_operation_name'])) { + $validationGroups = $resourceMetadata->getCollectionOperationAttribute($attributes['collection_operation_name'], 'validation_groups'); } else { - $validationGroups = $resourceMetadata->getItemOperationAttribute($itemOperationName, 'validation_groups'); + $validationGroups = $resourceMetadata->getItemOperationAttribute($attributes['item_operation_name'], 'validation_groups'); } if (!$validationGroups) { diff --git a/src/EventListener/DeserializerViewListener.php b/src/EventListener/DeserializerViewListener.php index f56e8f92a41..d406cdd7c26 100644 --- a/src/EventListener/DeserializerViewListener.php +++ b/src/EventListener/DeserializerViewListener.php @@ -50,18 +50,18 @@ public function onKernelView(GetResponseForControllerResultEvent $event) } try { - $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); + $attributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } - $context = $this->serializerContextBuilder->createFromRequest($request, false, $extractedAttributes); + $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes); if (null !== $controllerResult) { $context['object_to_populate'] = $controllerResult; } $event->setControllerResult( - $this->serializer->deserialize($request->getContent(), $extractedAttributes[0], $extractedAttributes[3], $context) + $this->serializer->deserialize($request->getContent(), $attributes['resource_class'], $attributes['format'], $context) ); } } diff --git a/src/EventListener/FormatRequestListener.php b/src/EventListener/FormatRequestListener.php index 4a3f6a67cab..ff94b6889d2 100644 --- a/src/EventListener/FormatRequestListener.php +++ b/src/EventListener/FormatRequestListener.php @@ -36,7 +36,7 @@ public function __construct(Negotiator $negotiator, array $formats) public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); - if (!$request->attributes->get('_resource_class')) { + if (null === $request->attributes->get('_api_resource_class')) { return; } diff --git a/src/EventListener/SerializerViewListener.php b/src/EventListener/SerializerViewListener.php index c398b48779b..d1eca75f8cc 100644 --- a/src/EventListener/SerializerViewListener.php +++ b/src/EventListener/SerializerViewListener.php @@ -14,6 +14,7 @@ use ApiPlatform\Core\Api\RequestAttributesExtractor; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\Serializer\SerializerInterface; @@ -48,12 +49,12 @@ public function onKernelView(GetResponseForControllerResultEvent $event) } try { - $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); + $attributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } - $context = $this->serializerContextBuilder->createFromRequest($request, true, $extractedAttributes); - $event->setControllerResult($this->serializer->serialize($controllerResult, $extractedAttributes[3], $context)); + $context = $this->serializerContextBuilder->createFromRequest($request, true, $attributes); + $event->setControllerResult($this->serializer->serialize($controllerResult, $attributes['format'], $context)); } } diff --git a/src/Hydra/EventListener/RequestExceptionListener.php b/src/Hydra/EventListener/RequestExceptionListener.php index 8cd42a13869..6b8de13d5ca 100644 --- a/src/Hydra/EventListener/RequestExceptionListener.php +++ b/src/Hydra/EventListener/RequestExceptionListener.php @@ -25,7 +25,7 @@ final class RequestExceptionListener extends ExceptionListener public function onKernelException(GetResponseForExceptionEvent $event) { // Normalize exceptions with hydra errors only for resources - if (!$event->getRequest()->attributes->has('_resource_class')) { + if (!$event->getRequest()->attributes->has('_api_resource_class')) { return; } diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index ce23fbb9cdd..36feb767d88 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -32,26 +32,24 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa /** * {@inheritdoc} */ - public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null) : array + public function createFromRequest(Request $request, bool $normalization, array $attributes = null) : array { - if (null === $extractedAttributes) { - $extractedAttributes = RequestAttributesExtractor::extractAttributes($request); + if (null === $attributes) { + $attributes = RequestAttributesExtractor::extractAttributes($request); } - list($resourceClass, $collectionOperationName, $itemOperationName) = $extractedAttributes; - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); $key = $normalization ? 'normalization_context' : 'denormalization_context'; - if ($collectionOperationName) { - $context = $resourceMetadata->getCollectionOperationAttribute($collectionOperationName, $key, [], true); - $context['collection_operation_name'] = $collectionOperationName; + if (isset($attributes['collection_operation_name'])) { + $context = $resourceMetadata->getCollectionOperationAttribute($attributes['collection_operation_name'], $key, [], true); + $context['collection_operation_name'] = $attributes['collection_operation_name']; } else { - $context = $resourceMetadata->getItemOperationAttribute($itemOperationName, $key, [], true); - $context['item_operation_name'] = $itemOperationName; + $context = $resourceMetadata->getItemOperationAttribute($attributes['item_operation_name'], $key, [], true); + $context['item_operation_name'] = $attributes['item_operation_name']; } - $context['resource_class'] = $resourceClass; + $context['resource_class'] = $attributes['resource_class']; $context['request_uri'] = $request->getRequestUri(); return $context; diff --git a/tests/Action/GetCollectionActionTest.php b/tests/Action/GetCollectionActionTest.php index 241b0656674..054cb2dda1f 100644 --- a/tests/Action/GetCollectionActionTest.php +++ b/tests/Action/GetCollectionActionTest.php @@ -27,7 +27,7 @@ public function testGetCollection() $dataProviderProphecy = $this->prophesize(CollectionDataProviderInterface::class); $dataProviderProphecy->getCollection('Foo', 'get')->willReturn($result); - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'get', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $action = new GetCollectionAction($dataProviderProphecy->reveal()); $this->assertSame($result, $action($request)); diff --git a/tests/Action/GetItemActionTest.php b/tests/Action/GetItemActionTest.php index d88c8482b01..c050c4f02ea 100644 --- a/tests/Action/GetItemActionTest.php +++ b/tests/Action/GetItemActionTest.php @@ -27,7 +27,7 @@ public function testGetItem() $dataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $dataProviderProphecy->getItem('Foo', 22, 'get', true)->willReturn($result); - $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $action = new GetItemAction($dataProviderProphecy->reveal()); $this->assertSame($result, $action($request, 22)); @@ -42,7 +42,7 @@ public function testNotFound() $dataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $dataProviderProphecy->getItem('Foo', 1312, 'get', true)->willReturn(null); - $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $action = new GetItemAction($dataProviderProphecy->reveal()); $action($request, 1312); diff --git a/tests/Api/RequestAttributesExtractorTest.php b/tests/Api/RequestAttributesExtractorTest.php index cdf492b9897..dd580143799 100644 --- a/tests/Api/RequestAttributesExtractorTest.php +++ b/tests/Api/RequestAttributesExtractorTest.php @@ -21,38 +21,44 @@ class RequestAttributesExtractorTest extends \PHPUnit_Framework_TestCase { public function testExtractCollectionAttributes() { - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'post', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $extactor = new RequestAttributesExtractor(); - $this->assertEquals(['Foo', 'post', null, 'json'], $extactor->extractAttributes($request)); + $this->assertEquals( + ['resource_class' => 'Foo', 'format' => 'json', 'mime_type' => 'application/json', 'collection_operation_name' => 'post'], + $extactor->extractAttributes($request) + ); } public function testExtractItemAttributes() { - $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $extactor = new RequestAttributesExtractor(); - $this->assertEquals(['Foo', null, 'get', 'json'], $extactor->extractAttributes($request)); + $this->assertEquals( + ['resource_class' => 'Foo', 'format' => 'json', 'mime_type' => 'application/json', 'item_operation_name' => 'get'], + $extactor->extractAttributes($request) + ); } /** * @expectedException \ApiPlatform\Core\Exception\RuntimeException - * @expectedExceptionMessage The request attribute "_resource_class" must be defined. + * @expectedExceptionMessage The request attribute "_api_resource_class" must be defined. */ public function testResourceClassNotSet() { - $request = new Request([], [], ['_item_operation_name' => 'get', '_api_format' => 'json']); + $request = new Request([], [], ['_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $extactor = new RequestAttributesExtractor(); $extactor->extractAttributes($request); } /** * @expectedException \ApiPlatform\Core\Exception\RuntimeException - * @expectedExceptionMessage One of the request attribute "_item_operation_name" or "_collection_operation_name" must be defined. + * @expectedExceptionMessage One of the request attribute "_api_collection_operation_name" or "_api_item_operation_name" must be defined. */ public function testOperationNotSet() { - $request = new Request([], [], ['_resource_class' => 'Foo', '_api_format' => 'json']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $extactor = new RequestAttributesExtractor(); $extactor->extractAttributes($request); } @@ -63,7 +69,18 @@ public function testOperationNotSet() */ public function testFormatNotSet() { - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'op']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'op']); + $extactor = new RequestAttributesExtractor(); + $extactor->extractAttributes($request); + } + + /** + * @expectedException \ApiPlatform\Core\Exception\RuntimeException + * @expectedExceptionMessage The request attribute "_api_mime_type" must be defined. + */ + public function testMimeTypeNotSet() + { + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'op', '_api_format' => 'json']); $extactor = new RequestAttributesExtractor(); $extactor->extractAttributes($request); } diff --git a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php index cb57ea5a2dd..5a4c491a50d 100644 --- a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php +++ b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php @@ -98,7 +98,7 @@ public function testNoMethodApiLoader() 'get' => ['method' => 'GET'], ]); - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); + $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); } /** @@ -117,7 +117,7 @@ public function testWrongMethodApiLoader() 'get' => ['method' => 'GET'], ]); - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); + $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); } /** @@ -163,24 +163,14 @@ private function getApiLoaderWithResourceMetadata(ResourceMetadata $resourceMeta return $apiLoader; } - /** - * get a Route instance with params. - * - * @param string path - * @param string controller - * @param string ressourceClass - * @param string operationName - * @param array methods - * @param bool collection - whether it's a collection or not - */ - private function getRoute($path, $controller, $resourceClass, $operationName, array $methods, $collection = false): Route + private function getRoute(string $path, string $controller, string $resourceClass, string $operationName, array $methods, bool $collection = false): Route { return new Route( $path, [ '_controller' => $controller, - '_resource_class' => $resourceClass, - sprintf('_%s_operation_name', $collection ? 'collection' : 'item') => $operationName, + '_api_resource_class' => $resourceClass, + sprintf('_api_%s_operation_name', $collection ? 'collection' : 'item') => $operationName, ], [], [], diff --git a/tests/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Bridge/Symfony/Routing/IriConverterTest.php index e21b8afcb92..1c89152764b 100644 --- a/tests/Bridge/Symfony/Routing/IriConverterTest.php +++ b/tests/Bridge/Symfony/Routing/IriConverterTest.php @@ -80,7 +80,7 @@ public function testGetItemFromIriItemNotFoundException() $routerMock = $this->prophesize(RouterInterface::class); $routerMock->match('/users/3')->willReturn([ - '_resource_class' => 'AppBundle\Entity\User', + '_api_resource_class' => 'AppBundle\Entity\User', 'id' => 3, ])->shouldBeCalledTimes(1); $itemDataProviderMock->getItem('AppBundle\Entity\User', 3, null, false)->shouldBeCalledTimes(1); @@ -102,7 +102,7 @@ public function testGetItemFromIri() $routerMock = $this->prophesize(RouterInterface::class); $routerMock->match('/users/3')->willReturn([ - '_resource_class' => 'AppBundle\Entity\User', + '_api_resource_class' => 'AppBundle\Entity\User', 'id' => 3, ])->shouldBeCalledTimes(1); $itemDataProviderMock->getItem('AppBundle\Entity\User', 3, null, true) diff --git a/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php b/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php index 0667480c287..6a3664340b5 100644 --- a/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php +++ b/tests/Bridge/Symfony/Validator/EventListener/ValidatorViewListenerTest.php @@ -83,8 +83,10 @@ private function createEventObject($expectedValidationGroups, $data) $kernel = $this->prophesize(HttpKernelInterface::class)->reveal(); $request = new Request([], [], [ - '_resource_class' => DummyEntity::class, - '_item_operation_name' => 'create', + '_api_resource_class' => DummyEntity::class, + '_api_item_operation_name' => 'create', + '_api_format' => 'json', + '_api_mime_type' => 'application/json', ]); $request->setMethod(Request::METHOD_POST); diff --git a/tests/EventListener/DeserializerViewListenerTest.php b/tests/EventListener/DeserializerViewListenerTest.php index 1632499e835..0bf0c1393f8 100644 --- a/tests/EventListener/DeserializerViewListenerTest.php +++ b/tests/EventListener/DeserializerViewListenerTest.php @@ -94,7 +94,7 @@ public function testDeserialize(string $method, bool $populateObject) $eventProphecy->getControllerResult()->willReturn($result); $eventProphecy->setControllerResult($result)->shouldBeCalled(); - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'post', '_api_format' => 'json'], [], [], [], '{}'); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_format' => 'json', '_api_mime_type' => 'application/json'], [], [], [], '{}'); $request->setMethod($method); $eventProphecy->getRequest()->willReturn($request); diff --git a/tests/EventListener/FormatRequestListenerTest.php b/tests/EventListener/FormatRequestListenerTest.php index 50228fa4927..477259b97a3 100644 --- a/tests/EventListener/FormatRequestListenerTest.php +++ b/tests/EventListener/FormatRequestListenerTest.php @@ -39,7 +39,7 @@ public function testNoResourceClass() public function testSupportedRequestFormat() { $request = new Request(); - $request->attributes->set('_resource_class', 'Foo'); + $request->attributes->set('_api_resource_class', 'Foo'); $request->setRequestFormat('xml'); $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -56,7 +56,7 @@ public function testSupportedRequestFormat() public function testUnsupportedRequestFormat() { $request = new Request(); - $request->attributes->set('_resource_class', 'Foo'); + $request->attributes->set('_api_resource_class', 'Foo'); $request->setRequestFormat('xml'); $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -73,7 +73,7 @@ public function testUnsupportedRequestFormat() public function testSupportedAcceptHeader() { $request = new Request(); - $request->attributes->set('_resource_class', 'Foo'); + $request->attributes->set('_api_resource_class', 'Foo'); $request->headers->set('Accept', 'text/html, application/xhtml+xml, application/xml, application/json;q=0.9, */*;q=0.8'); $eventProphecy = $this->prophesize(GetResponseEvent::class); @@ -90,7 +90,7 @@ public function testSupportedAcceptHeader() public function testUnsupportedAcceptHeader() { $request = new Request(); - $request->attributes->set('_resource_class', 'Foo'); + $request->attributes->set('_api_resource_class', 'Foo'); $request->headers->set('Accept', 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8'); $eventProphecy = $this->prophesize(GetResponseEvent::class); diff --git a/tests/EventListener/SerializerViewListenerTest.php b/tests/EventListener/SerializerViewListenerTest.php index f5eced33269..c4e47b45daf 100644 --- a/tests/EventListener/SerializerViewListenerTest.php +++ b/tests/EventListener/SerializerViewListenerTest.php @@ -63,7 +63,7 @@ public function testDoNotSerializeWhenResourceClassNotSet() $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); $eventProphecy->getControllerResult()->willReturn(new \stdClass()); - $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_collection_operation_name' => 'get'])); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_api_mime_type' => 'text/xml', '_api_collection_operation_name' => 'get'])); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); @@ -79,7 +79,7 @@ public function testDoNotSerializeWhenOperationNotSet() $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); $eventProphecy->getControllerResult()->willReturn(new \stdClass()); - $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo'])); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_api_mime_type' => 'text/xml', '_api_resource_class' => 'Foo'])); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled(); @@ -96,7 +96,7 @@ public function testSerializeCollectionOperation() $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); $eventProphecy->getControllerResult()->willReturn(new \stdClass()); - $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_collection_operation_name' => 'get'])); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_api_mime_type' => 'text/xml', '_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get'])); $eventProphecy->setControllerResult('bar')->shouldBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); @@ -114,7 +114,7 @@ public function testSerializeItemOperation() $eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class); $eventProphecy->getControllerResult()->willReturn(new \stdClass()); - $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_resource_class' => 'Foo', '_item_operation_name' => 'get'])); + $eventProphecy->getRequest()->willReturn(new Request([], [], ['_api_format' => 'xml', '_api_mime_type' => 'text/xml', '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get'])); $eventProphecy->setControllerResult('bar')->shouldBeCalled(); $serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); diff --git a/tests/Fixtures/TestBundle/Controller/ConfigCustomController.php b/tests/Fixtures/TestBundle/Controller/ConfigCustomController.php index 808f0a10613..430058a7450 100644 --- a/tests/Fixtures/TestBundle/Controller/ConfigCustomController.php +++ b/tests/Fixtures/TestBundle/Controller/ConfigCustomController.php @@ -22,9 +22,6 @@ */ class ConfigCustomController { - /** - * @var DataProviderInterface - */ private $dataProvider; public function __construct(ItemDataProviderInterface $dataProvider) @@ -34,8 +31,8 @@ public function __construct(ItemDataProviderInterface $dataProvider) public function __invoke(Request $request, $id) { - list($resourceType) = RequestAttributesExtractor::extractAttributes($request); + $attributes = RequestAttributesExtractor::extractAttributes($request); - return $this->dataProvider->getItem($resourceType, $id); + return $this->dataProvider->getItem($attributes['resource_class'], $id); } } diff --git a/tests/Serializer/SerializerContextBuilderTest.php b/tests/Serializer/SerializerContextBuilderTest.php index 099bf44fb14..e3eef1a7d19 100644 --- a/tests/Serializer/SerializerContextBuilderTest.php +++ b/tests/Serializer/SerializerContextBuilderTest.php @@ -45,19 +45,19 @@ protected function setUp() public function testCreateFromRequest() { - $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'xml']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']); $expected = ['foo' => 'bar', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; $this->assertEquals($expected, $this->builder->createFromRequest($request, true)); - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'pot', '_api_format' => 'xml']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'pot', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']); $expected = ['foo' => 'bar', 'collection_operation_name' => 'pot', 'resource_class' => 'Foo', 'request_uri' => '']; $this->assertEquals($expected, $this->builder->createFromRequest($request, true)); - $request = new Request([], [], ['_resource_class' => 'Foo', '_item_operation_name' => 'get', '_api_format' => 'xml']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']); $expected = ['bar' => 'baz', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; $this->assertEquals($expected, $this->builder->createFromRequest($request, false)); - $request = new Request([], [], ['_resource_class' => 'Foo', '_collection_operation_name' => 'post', '_api_format' => 'xml']); + $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']); $expected = ['bar' => 'baz', 'collection_operation_name' => 'post', 'resource_class' => 'Foo', 'request_uri' => '']; $this->assertEquals($expected, $this->builder->createFromRequest($request, false)); } @@ -73,6 +73,6 @@ public function testThrowExceptionOnInvalidRequest() public function testReuseExistingAttributes() { $expected = ['bar' => 'baz', 'item_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '']; - $this->assertEquals($expected, $this->builder->createFromRequest(new Request(), false, ['Foo', null, 'get'])); + $this->assertEquals($expected, $this->builder->createFromRequest(new Request(), false, ['resource_class' => 'Foo', 'item_operation_name' => 'get'])); } }