From b88c5622ce1c52c1b319f12eb5b1c2d78190c183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 15 Jul 2016 17:49:35 +0200 Subject: [PATCH 1/2] Refactor the entrypoint system --- src/Action/EntrypointAction.php | 13 +++---- .../Symfony/Bundle/Resources/config/api.xml | 6 ++++ .../Symfony/Bundle/Resources/config/hydra.xml | 23 ++++++------ .../config/routing/{hal.xml => api.xml} | 3 +- .../Bundle/Resources/config/routing/hydra.xml | 8 ----- .../Resources/config/routing/jsonld.xml | 8 ----- src/Bridge/Symfony/Routing/ApiLoader.php | 23 +++++++++--- src/EventListener/SerializeListener.php | 36 ++++++++++++++----- src/Hydra/ApiDocumentationBuilder.php | 2 +- .../ResourceNameCollectionNormalizer.php} | 25 +++++++++---- src/JsonLd/EntrypointBuilderInterface.php | 29 --------------- .../ApiPlatformExtensionTest.php | 3 +- .../Bridge/Symfony/Routing/ApiLoaderTest.php | 5 +-- 13 files changed, 94 insertions(+), 90 deletions(-) rename src/Bridge/Symfony/Bundle/Resources/config/routing/{hal.xml => api.xml} (85%) rename src/Hydra/{EntrypointBuilder.php => Serializer/ResourceNameCollectionNormalizer.php} (74%) delete mode 100644 src/JsonLd/EntrypointBuilderInterface.php diff --git a/src/Action/EntrypointAction.php b/src/Action/EntrypointAction.php index 202f164e43f..4f9dd391d7f 100644 --- a/src/Action/EntrypointAction.php +++ b/src/Action/EntrypointAction.php @@ -11,7 +11,8 @@ namespace ApiPlatform\Core\Action; -use ApiPlatform\Core\JsonLd\EntrypointBuilderInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; /** * Generates the API entrypoint. @@ -20,15 +21,15 @@ */ final class EntrypointAction { - private $entrypointBuilder; + private $resourceNameCollectionFactory; - public function __construct(EntrypointBuilderInterface $entrypointBuilder) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) { - $this->entrypointBuilder = $entrypointBuilder; + $this->resourceNameCollectionFactory = $resourceNameCollection; } - public function __invoke() : array + public function __invoke() : ResourceNameCollection { - return $this->entrypointBuilder->getEntrypoint(); + return $this->resourceNameCollectionFactory->create(); } } diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 40dbb25b446..4aa2ebfbc93 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -25,6 +25,8 @@ + %api_platform.formats% + @@ -114,6 +116,10 @@ + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml index 995b0966e6b..87745012627 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml @@ -5,13 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - - @@ -49,12 +42,21 @@ + + + + + + + + + - + @@ -82,11 +84,8 @@ - - - - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/hal.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml similarity index 85% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/hal.xml rename to src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml index 8615a58eb6f..0a04cf423c8 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/hal.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml @@ -5,8 +5,9 @@ xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + api_platform.action.entrypoint + 1 index index diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/hydra.xml index 2a0c5902210..b877b8254e9 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/hydra.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/routing/hydra.xml @@ -5,14 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - api_platform.action.entrypoint - 1 - jsonld - index - index - - api_platform.hydra.action.documentation 1 diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml index b2814ac45d6..ce129f51816 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml @@ -5,14 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - api_platform.action.entrypoint - 1 - jsonld - index - index - - api_platform.jsonld.action.context 1 diff --git a/src/Bridge/Symfony/Routing/ApiLoader.php b/src/Bridge/Symfony/Routing/ApiLoader.php index 4c066ec0559..b16469b9ce1 100644 --- a/src/Bridge/Symfony/Routing/ApiLoader.php +++ b/src/Bridge/Symfony/Routing/ApiLoader.php @@ -40,14 +40,16 @@ final class ApiLoader extends Loader private $resourceMetadataFactory; private $resourcePathGenerator; private $container; + private $formats; - public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathNamingStrategyInterface $resourcePathGenerator, ContainerInterface $container) + public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathNamingStrategyInterface $resourcePathGenerator, ContainerInterface $container, array $formats) { $this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing'))); $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; $this->resourcePathGenerator = $resourcePathGenerator; $this->container = $container; + $this->formats = $formats; } /** @@ -57,9 +59,7 @@ public function load($data, $type = null) { $routeCollection = new RouteCollection(); - $routeCollection->addCollection($this->fileLoader->load('hal.xml')); - $routeCollection->addCollection($this->fileLoader->load('jsonld.xml')); - $routeCollection->addCollection($this->fileLoader->load('hydra.xml')); + $this->loadExternalFiles($routeCollection); if ($this->container->getParameter('api_platform.enable_swagger')) { $routeCollection->addCollection($this->fileLoader->load('swagger.xml')); @@ -92,6 +92,21 @@ public function supports($resource, $type = null) return 'api_platform' === $type; } + /** + * Load external files. + * + * @param RouteCollection $routeCollection + */ + private function loadExternalFiles(RouteCollection $routeCollection) + { + $routeCollection->addCollection($this->fileLoader->load('api.xml')); + + if (isset($this->formats['jsonld'])) { + $routeCollection->addCollection($this->fileLoader->load('jsonld.xml')); + $routeCollection->addCollection($this->fileLoader->load('hydra.xml')); + } + } + /** * Creates and adds a route for the given operation to the route collection. * diff --git a/src/EventListener/SerializeListener.php b/src/EventListener/SerializeListener.php index 6f013e9bad5..c07e222e332 100644 --- a/src/EventListener/SerializeListener.php +++ b/src/EventListener/SerializeListener.php @@ -14,6 +14,7 @@ use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\Serializer\Encoder\EncoderInterface; @@ -49,17 +50,10 @@ public function onKernelView(GetResponseForControllerResultEvent $event) return; } - if ($request->attributes->get('_api_respond') && !is_object($controllerResult)) { - if (!$this->serializer instanceof EncoderInterface) { - throw new RuntimeException('The serializer instance must implements the "%s" interface.', EncoderInterface::class); - } - - $event->setControllerResult($this->serializer->encode($controllerResult, $request->getRequestFormat())); - } - try { $attributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { + $this->serializeRawData($event, $request, $controllerResult); return; } @@ -68,4 +62,30 @@ public function onKernelView(GetResponseForControllerResultEvent $event) $event->setControllerResult($this->serializer->serialize($controllerResult, $request->getRequestFormat(), $context)); } + + /** + * Tries to serialize data that are not API resources (e.g. the entrypoint or data returned by a custom controller). + * + * @param GetResponseForControllerResultEvent $event + * @param Request $request + * @param object $controllerResult + */ + private function serializeRawData(GetResponseForControllerResultEvent $event, Request $request, $controllerResult) + { + if (!$request->attributes->get('_api_respond')) { + return; + } + + if (is_object($controllerResult)) { + $event->setControllerResult($this->serializer->serialize($controllerResult, $request->getRequestFormat())); + + return; + } + + if (!$this->serializer instanceof EncoderInterface) { + throw new RuntimeException('The serializer instance must implements the "%s" interface.', EncoderInterface::class); + } + + $event->setControllerResult($this->serializer->encode($controllerResult, $request->getRequestFormat())); + } } diff --git a/src/Hydra/ApiDocumentationBuilder.php b/src/Hydra/ApiDocumentationBuilder.php index 636a7912731..d91375ebb48 100644 --- a/src/Hydra/ApiDocumentationBuilder.php +++ b/src/Hydra/ApiDocumentationBuilder.php @@ -248,7 +248,7 @@ public function getApiDocumentation() : array $doc['hydra:description'] = $this->description; } - $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_hydra_entrypoint'); + $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint'); $doc['hydra:supportedClass'] = $classes; return $doc; diff --git a/src/Hydra/EntrypointBuilder.php b/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php similarity index 74% rename from src/Hydra/EntrypointBuilder.php rename to src/Hydra/Serializer/ResourceNameCollectionNormalizer.php index 7ff2039af20..c1ceeca8827 100644 --- a/src/Hydra/EntrypointBuilder.php +++ b/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php @@ -9,22 +9,25 @@ * file that was distributed with this source code. */ -namespace ApiPlatform\Core\Hydra; +namespace ApiPlatform\Core\Hydra\Serializer; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\UrlGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\JsonLd\EntrypointBuilderInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** - * {@inheritdoc} + * Normalizes the API entrypoint. * * @author Kévin Dunglas */ -final class EntrypointBuilder implements EntrypointBuilderInterface +final class ResourceNameCollectionNormalizer implements NormalizerInterface { + const FORMAT = 'jsonld'; + private $resourceNameCollectionFactory; private $resourceMetadataFactory; private $iriConverter; @@ -41,11 +44,11 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName /** * {@inheritdoc} */ - public function getEntrypoint(string $referenceType = UrlGeneratorInterface::ABS_PATH) : array + public function normalize($object, $format = null, array $context = array()) { $entrypoint = [ - '@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'Entrypoint'], $referenceType), - '@id' => $this->urlGenerator->generate('api_hydra_entrypoint', [], $referenceType), + '@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'Entrypoint']), + '@id' => $this->urlGenerator->generate('api_entrypoint'), '@type' => 'Entrypoint', ]; @@ -64,4 +67,12 @@ public function getEntrypoint(string $referenceType = UrlGeneratorInterface::ABS return $entrypoint; } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null) + { + return self::FORMAT === $format && $data instanceof ResourceNameCollection; + } } diff --git a/src/JsonLd/EntrypointBuilderInterface.php b/src/JsonLd/EntrypointBuilderInterface.php deleted file mode 100644 index e1fe43ed256..00000000000 --- a/src/JsonLd/EntrypointBuilderInterface.php +++ /dev/null @@ -1,29 +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 ApiPlatform\Core\Api\UrlGeneratorInterface; - -/** - * API entrypoint builder interface. - * - * @author Kévin Dunglas - */ -interface EntrypointBuilderInterface -{ - /** - * Gets the entrypoint content of the API. - * - * @return array - */ - public function getEntrypoint(string $referenceType = UrlGeneratorInterface::ABS_PATH) : array; -} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 9b9d6e9dd77..86e8129eb96 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -279,7 +279,6 @@ private function getContainerBuilderProphecy() 'api_platform.doctrine.orm.query_extension.pagination', 'api_platform.doctrine.orm.query_extension.order', 'api_platform.doctrine.listener.view.write', - 'api_platform.jsonld.context_builder', 'api_platform.jsonld.normalizer.item', 'api_platform.jsonld.encoder', 'api_platform.jsonld.action.context', @@ -295,10 +294,10 @@ private function getContainerBuilderProphecy() 'api_platform.hydra.action.documentation', 'api_platform.hydra.action.exception', 'api_platform.hydra.documentation_builder', - 'api_platform.hydra.entrypoint_builder', 'api_platform.hydra.listener.response.add_link_header', 'api_platform.hydra.listener.exception.validation', 'api_platform.hydra.listener.exception', + 'api_platform.hydra.normalizer.resource_name_collection', 'api_platform.hydra.normalizer.collection', 'api_platform.hydra.normalizer.partial_collection_view', 'api_platform.hydra.normalizer.collection_filters', diff --git a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php index 3444df54207..937d60b0e03 100644 --- a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php +++ b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php @@ -32,16 +32,13 @@ class ApiLoaderTest extends \PHPUnit_Framework_TestCase public function testApiLoader() { $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - //default operation based on OperationResourceMetadataFactory $resourceMetadata = $resourceMetadata->withItemOperations([ 'get' => ['method' => 'GET'], 'put' => ['method' => 'PUT'], 'delete' => ['method' => 'DELETE'], ]); - //custom operations $resourceMetadata = $resourceMetadata->withCollectionOperations([ 'my_op' => ['method' => 'GET', 'controller' => 'some.service.name'], //with controller @@ -159,7 +156,7 @@ private function getApiLoaderWithResourceMetadata(ResourceMetadata $resourceMeta $resourcePathGeneratorProphecy = $this->prophesize(ResourcePathNamingStrategyInterface::class); $resourcePathGeneratorProphecy->generateResourceBasePath('dummy')->willReturn('dummies'); - $apiLoader = new ApiLoader($kernelProphecy->reveal(), $resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $resourcePathGeneratorProphecy->reveal(), $containerProphecy->reveal()); + $apiLoader = new ApiLoader($kernelProphecy->reveal(), $resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $resourcePathGeneratorProphecy->reveal(), $containerProphecy->reveal(), ['jsonld' => ['application/ld+json']]); return $apiLoader; } From ac4a097131c4bb22c800f21d7da080bd92660771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 15 Jul 2016 18:23:58 +0200 Subject: [PATCH 2/2] Add HAL support for the entrypoint --- features/hal.feature | 9 +++ .../Symfony/Bundle/Resources/config/hal.xml | 23 +++--- .../Symfony/Bundle/Resources/config/hydra.xml | 1 - src/EventListener/SerializeListener.php | 1 + .../ResourceNameCollectionNormalizer.php | 71 +++++++++++++++++++ .../ResourceNameCollectionNormalizer.php | 9 +-- src/Serializer/ItemNormalizer.php | 2 +- src/Swagger/ApiDocumentationBuilder.php | 1 - .../ApiPlatformExtensionTest.php | 1 + tests/Hal/Serializer/ItemNormalizerTest.php | 2 +- .../ResourceNameCollectionNormalizerTest.php | 69 ++++++++++++++++++ .../ResourceNameCollectionNormalizerTest.php | 66 +++++++++++++++++ 12 files changed, 237 insertions(+), 18 deletions(-) create mode 100644 src/Hal/Serializer/ResourceNameCollectionNormalizer.php create mode 100644 tests/Hal/Serializer/ResourceNameCollectionNormalizerTest.php create mode 100644 tests/Hydra/Serializer/ResourceNameCollectionNormalizerTest.php diff --git a/features/hal.feature b/features/hal.feature index 8c65b6ebd0b..08731cb1a7d 100644 --- a/features/hal.feature +++ b/features/hal.feature @@ -4,6 +4,15 @@ Feature: HAL support I need to be able to retrieve valid HAL responses. @createSchema + Scenario: Retrieve the API entrypoint + When I add "Accept" header equal to "application/hal+json" + And I send a "GET" request to "/" + 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/hal+json" + And the JSON node "_links.self.href" should be equal to "/" + And the JSON node "_links.dummy.href" should be equal to "/dummies" + Scenario: Create a third level When I add "Content-Type" header equal to "application/json" And I send a "POST" request to "/third_levels" with body: diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml index 8c1a411f333..19b17099a87 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml @@ -5,13 +5,27 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - jsonhal + + + + + + + + + + + %api_platform.collection.pagination.page_parameter_name% + + + + @@ -23,13 +37,6 @@ - - - - %api_platform.collection.pagination.page_parameter_name% - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml index 87745012627..4e5607ce539 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml @@ -43,7 +43,6 @@ - diff --git a/src/EventListener/SerializeListener.php b/src/EventListener/SerializeListener.php index c07e222e332..2f4b35a7590 100644 --- a/src/EventListener/SerializeListener.php +++ b/src/EventListener/SerializeListener.php @@ -54,6 +54,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event) $attributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { $this->serializeRawData($event, $request, $controllerResult); + return; } diff --git a/src/Hal/Serializer/ResourceNameCollectionNormalizer.php b/src/Hal/Serializer/ResourceNameCollectionNormalizer.php new file mode 100644 index 00000000000..b986d79bd33 --- /dev/null +++ b/src/Hal/Serializer/ResourceNameCollectionNormalizer.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Hal\Serializer; + +use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\UrlGeneratorInterface; +use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * Normalizes the API entrypoint. + * + * @author Kévin Dunglas + */ +final class ResourceNameCollectionNormalizer implements NormalizerInterface +{ + const FORMAT = 'jsonhal'; + + private $resourceMetadataFactory; + private $iriConverter; + private $urlGenerator; + + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) + { + $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->iriConverter = $iriConverter; + $this->urlGenerator = $urlGenerator; + } + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = null, array $context = []) + { + $entrypoint = ['_links' => ['self' => ['href' => $this->urlGenerator->generate('api_entrypoint')]]]; + + foreach ($object as $resourceClass) { + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + + if (empty($resourceMetadata->getCollectionOperations())) { + continue; + } + try { + $entrypoint['_links'][lcfirst($resourceMetadata->getShortName())]['href'] = $this->iriConverter->getIriFromResourceClass($resourceClass); + } catch (InvalidArgumentException $ex) { + // Ignore resources without GET operations + } + } + + return $entrypoint; + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null) + { + return self::FORMAT === $format && $data instanceof ResourceNameCollection; + } +} diff --git a/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php b/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php index c1ceeca8827..245c2341c00 100644 --- a/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php +++ b/src/Hydra/Serializer/ResourceNameCollectionNormalizer.php @@ -15,7 +15,6 @@ use ApiPlatform\Core\Api\UrlGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -28,14 +27,12 @@ final class ResourceNameCollectionNormalizer implements NormalizerInterface { const FORMAT = 'jsonld'; - private $resourceNameCollectionFactory; private $resourceMetadataFactory; private $iriConverter; private $urlGenerator; - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; $this->iriConverter = $iriConverter; $this->urlGenerator = $urlGenerator; @@ -44,7 +41,7 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName /** * {@inheritdoc} */ - public function normalize($object, $format = null, array $context = array()) + public function normalize($object, $format = null, array $context = []) { $entrypoint = [ '@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'Entrypoint']), @@ -52,7 +49,7 @@ public function normalize($object, $format = null, array $context = array()) '@type' => 'Entrypoint', ]; - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { + foreach ($object as $resourceClass) { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); if (empty($resourceMetadata->getCollectionOperations())) { diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index c61b70802fb..d3d42ab8f2d 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -45,6 +45,6 @@ public function denormalize($data, $class, $format = null, array $context = []) $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], true); } - return parent::denormalize($data, $class, $format, $context); // TODO: Change the autogenerated stub + return parent::denormalize($data, $class, $format, $context); } } diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index 550d6721d6f..cc412647943 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -180,7 +180,6 @@ public function getApiDocumentation() : array } } - $resourceClassIri .= '/{id}'; $itemOperationsDocs[$resourceClassIri] = $operation['item']; diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 86e8129eb96..55123417217 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -289,6 +289,7 @@ private function getContainerBuilderProphecy() 'api_platform.swagger.action.documentation', 'api_platform.swagger.action.ui', 'api_platform.hal.encoder', + 'api_platform.hal.normalizer.resource_name_collection', 'api_platform.hal.normalizer.item', 'api_platform.hal.normalizer.collection', 'api_platform.hydra.action.documentation', diff --git a/tests/Hal/Serializer/ItemNormalizerTest.php b/tests/Hal/Serializer/ItemNormalizerTest.php index c786a3d429e..7d5f67000a9 100644 --- a/tests/Hal/Serializer/ItemNormalizerTest.php +++ b/tests/Hal/Serializer/ItemNormalizerTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace ApiPlatform\Core\Tests\Hal; +namespace ApiPlatform\Core\Tests\Hal\Serializer; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; diff --git a/tests/Hal/Serializer/ResourceNameCollectionNormalizerTest.php b/tests/Hal/Serializer/ResourceNameCollectionNormalizerTest.php new file mode 100644 index 00000000000..0cc89cc93f3 --- /dev/null +++ b/tests/Hal/Serializer/ResourceNameCollectionNormalizerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Hal\Serializer; + +use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\UrlGeneratorInterface; +use ApiPlatform\Core\Hal\Serializer\ResourceNameCollectionNormalizer; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; + +/** + * @author Kévin Dunglas + */ +class ResourceNameCollectionNormalizerTest extends \PHPUnit_Framework_TestCase +{ + public function testSupportNormalization() + { + $collection = new ResourceNameCollection(); + + $factoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + + $normalizer = new ResourceNameCollectionNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + + $this->assertTrue($normalizer->supportsNormalization($collection, ResourceNameCollectionNormalizer::FORMAT)); + $this->assertFalse($normalizer->supportsNormalization($collection, 'json')); + $this->assertFalse($normalizer->supportsNormalization(new \stdClass(), ResourceNameCollectionNormalizer::FORMAT)); + } + + public function testNormalize() + { + $collection = new ResourceNameCollection([Dummy::class]); + + $factoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $factoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('Dummy', null, null, null, ['get']))->shouldBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromResourceClass(Dummy::class)->willReturn('/api/dummies')->shouldBeCalled(); + + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + $urlGeneratorProphecy->generate('api_entrypoint')->willReturn('/api')->shouldBeCalled(); + + $normalizer = new ResourceNameCollectionNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/api', + ], + 'dummy' => [ + 'href' => '/api/dummies', + ], + ], + ]; + $this->assertEquals($expected, $normalizer->normalize($collection, ResourceNameCollectionNormalizer::FORMAT)); + } +} diff --git a/tests/Hydra/Serializer/ResourceNameCollectionNormalizerTest.php b/tests/Hydra/Serializer/ResourceNameCollectionNormalizerTest.php new file mode 100644 index 00000000000..d18f4ab56f7 --- /dev/null +++ b/tests/Hydra/Serializer/ResourceNameCollectionNormalizerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Hydra\Serializer; + +use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\UrlGeneratorInterface; +use ApiPlatform\Core\Hydra\Serializer\ResourceNameCollectionNormalizer; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; + +/** + * @author Kévin Dunglas + */ +class ResourceNameCollectionNormalizerTest extends \PHPUnit_Framework_TestCase +{ + public function testSupportNormalization() + { + $collection = new ResourceNameCollection(); + + $factoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + + $normalizer = new ResourceNameCollectionNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + + $this->assertTrue($normalizer->supportsNormalization($collection, ResourceNameCollectionNormalizer::FORMAT)); + $this->assertFalse($normalizer->supportsNormalization($collection, 'json')); + $this->assertFalse($normalizer->supportsNormalization(new \stdClass(), ResourceNameCollectionNormalizer::FORMAT)); + } + + public function testNormalize() + { + $collection = new ResourceNameCollection([Dummy::class]); + + $factoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $factoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('Dummy', null, null, null, ['get']))->shouldBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromResourceClass(Dummy::class)->willReturn('/api/dummies')->shouldBeCalled(); + + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + $urlGeneratorProphecy->generate('api_entrypoint')->willReturn('/api')->shouldBeCalled(); + $urlGeneratorProphecy->generate('api_jsonld_context', ['shortName' => 'Entrypoint'])->willReturn('/context/Entrypoint')->shouldBeCalled(); + + $normalizer = new ResourceNameCollectionNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + + $expected = [ + '@context' => '/context/Entrypoint', + '@id' => '/api', + '@type' => 'Entrypoint', + 'dummy' => '/api/dummies', + ]; + $this->assertEquals($expected, $normalizer->normalize($collection, ResourceNameCollectionNormalizer::FORMAT)); + } +}