Skip to content

Commit 7d275f0

Browse files
authored
Merge pull request #625 from dunglas/refactor_entrypoint
Refactor entrypoint and add HAL support
2 parents 38ff44c + ac4a097 commit 7d275f0

File tree

21 files changed

+329
-106
lines changed

21 files changed

+329
-106
lines changed

features/hal.feature

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ Feature: HAL support
44
I need to be able to retrieve valid HAL responses.
55

66
@createSchema
7+
Scenario: Retrieve the API entrypoint
8+
When I add "Accept" header equal to "application/hal+json"
9+
And I send a "GET" request to "/"
10+
Then the response status code should be 200
11+
And the response should be in JSON
12+
And the header "Content-Type" should be equal to "application/hal+json"
13+
And the JSON node "_links.self.href" should be equal to "/"
14+
And the JSON node "_links.dummy.href" should be equal to "/dummies"
15+
716
Scenario: Create a third level
817
When I add "Content-Type" header equal to "application/json"
918
And I send a "POST" request to "/third_levels" with body:

src/Action/EntrypointAction.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
namespace ApiPlatform\Core\Action;
1313

14-
use ApiPlatform\Core\JsonLd\EntrypointBuilderInterface;
14+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
15+
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
1516

1617
/**
1718
* Generates the API entrypoint.
@@ -20,15 +21,15 @@
2021
*/
2122
final class EntrypointAction
2223
{
23-
private $entrypointBuilder;
24+
private $resourceNameCollectionFactory;
2425

25-
public function __construct(EntrypointBuilderInterface $entrypointBuilder)
26+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection)
2627
{
27-
$this->entrypointBuilder = $entrypointBuilder;
28+
$this->resourceNameCollectionFactory = $resourceNameCollection;
2829
}
2930

30-
public function __invoke() : array
31+
public function __invoke() : ResourceNameCollection
3132
{
32-
return $this->entrypointBuilder->getEntrypoint();
33+
return $this->resourceNameCollectionFactory->create();
3334
}
3435
}

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
2626
<argument type="service" id="api_platform.naming.resource_path_naming_strategy" />
2727
<argument type="service" id="service_container" />
28+
<argument>%api_platform.formats%</argument>
29+
2830
<tag name="routing.loader" />
2931
</service>
3032

@@ -114,6 +116,10 @@
114116
<service id="api_platform.action.get_item" alias="api_platform.action.placeholder" />
115117
<service id="api_platform.action.put_item" alias="api_platform.action.placeholder" />
116118
<service id="api_platform.action.delete_item" alias="api_platform.action.placeholder" />
119+
120+
<service id="api_platform.action.entrypoint" class="ApiPlatform\Core\Action\EntrypointAction">
121+
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
122+
</service>
117123
</services>
118124

119125
</container>

src/Bridge/Symfony/Bundle/Resources/config/hal.xml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,27 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8-
<!-- Serializer -->
98
<service id="api_platform.hal.encoder" class="ApiPlatform\Core\Serializer\JsonEncoder" public="false">
109
<argument>jsonhal</argument>
1110

1211
<tag name="serializer.encoder" />
1312
</service>
1413

14+
<service id="api_platform.hal.normalizer.resource_name_collection" class="ApiPlatform\Core\Hal\Serializer\ResourceNameCollectionNormalizer" public="false">
15+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
16+
<argument type="service" id="api_platform.iri_converter" />
17+
<argument type="service" id="api_platform.router" />
18+
19+
<tag name="serializer.normalizer" priority="32" />
20+
</service>
21+
22+
<service id="api_platform.hal.normalizer.collection" class="ApiPlatform\Core\Hal\Serializer\CollectionNormalizer" public="false">
23+
<argument type="service" id="api_platform.resource_class_resolver" />
24+
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
25+
26+
<tag name="serializer.normalizer" priority="16" />
27+
</service>
28+
1529
<service id="api_platform.hal.normalizer.item" class="ApiPlatform\Core\Hal\Serializer\ItemNormalizer" public="false">
1630
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
1731
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
@@ -23,13 +37,6 @@
2337

2438
<tag name="serializer.normalizer" priority="8" />
2539
</service>
26-
27-
<service id="api_platform.hal.normalizer.collection" class="ApiPlatform\Core\Hal\Serializer\CollectionNormalizer" public="false">
28-
<argument type="service" id="api_platform.resource_class_resolver" />
29-
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
30-
31-
<tag name="serializer.normalizer" priority="50" />
32-
</service>
3340
</services>
3441

3542
</container>

src/Bridge/Symfony/Bundle/Resources/config/hydra.xml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8-
<service id="api_platform.hydra.entrypoint_builder" class="ApiPlatform\Core\Hydra\EntrypointBuilder" public="false">
9-
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
10-
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
11-
<argument type="service" id="api_platform.iri_converter" />
12-
<argument type="service" id="api_platform.router" />
13-
</service>
14-
158
<service id="api_platform.hydra.documentation_builder" class="ApiPlatform\Core\Hydra\ApiDocumentationBuilder" public="false">
169
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
1710
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
@@ -48,12 +41,20 @@
4841

4942
<!-- Serializer -->
5043

44+
<service id="api_platform.hydra.normalizer.resource_name_collection" class="ApiPlatform\Core\Hydra\Serializer\ResourceNameCollectionNormalizer" public="false">
45+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
46+
<argument type="service" id="api_platform.iri_converter" />
47+
<argument type="service" id="api_platform.router" />
48+
49+
<tag name="serializer.normalizer" priority="32" />
50+
</service>
51+
5152
<service id="api_platform.hydra.normalizer.collection" class="ApiPlatform\Core\Hydra\Serializer\CollectionNormalizer" public="false">
5253
<argument type="service" id="api_platform.jsonld.context_builder" />
5354
<argument type="service" id="api_platform.resource_class_resolver" />
5455
<argument type="service" id="api_platform.iri_converter" />
5556

56-
<tag name="serializer.normalizer" priority="50" />
57+
<tag name="serializer.normalizer" priority="16" />
5758
</service>
5859

5960
<service id="api_platform.hydra.normalizer.constraint_violation_list" class="ApiPlatform\Core\Bridge\Symfony\Validator\Hydra\Serializer\ConstraintViolationListNormalizer" public="false">
@@ -81,11 +82,8 @@
8182
<argument type="service" id="api_platform.resource_class_resolver" />
8283
<argument type="service" id="api_platform.filters" />
8384
</service>
84-
<!-- Action -->
8585

86-
<service id="api_platform.action.entrypoint" class="ApiPlatform\Core\Action\EntrypointAction">
87-
<argument type="service" id="api_platform.hydra.entrypoint_builder" />
88-
</service>
86+
<!-- Action -->
8987

9088
<service id="api_platform.hydra.action.documentation" class="ApiPlatform\Core\Documentation\Action\DocumentationAction">
9189
<argument type="service" id="api_platform.hydra.documentation_builder" />

src/Bridge/Symfony/Bundle/Resources/config/routing/hal.xml renamed to src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
xsi:schemaLocation="http://symfony.com/schema/routing
66
http://symfony.com/schema/routing/routing-1.0.xsd">
77

8-
<route id="api_hal_entrypoint" path="/{index}.jsonhal">
8+
<route id="api_entrypoint" path="/{index}.{_format}">
99
<default key="_controller">api_platform.action.entrypoint</default>
10+
<default key="_format" />
1011
<default key="_api_respond">1</default>
1112
<default key="index">index</default>
1213
<requirement key="index">index</requirement>

src/Bridge/Symfony/Bundle/Resources/config/routing/hydra.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@
55
xsi:schemaLocation="http://symfony.com/schema/routing
66
http://symfony.com/schema/routing/routing-1.0.xsd">
77

8-
<route id="api_hydra_entrypoint" path="/{index}.{_format}">
9-
<default key="_controller">api_platform.action.entrypoint</default>
10-
<default key="_api_respond">1</default>
11-
<default key="_format">jsonld</default>
12-
<default key="index">index</default>
13-
<requirement key="index">index</requirement>
14-
</route>
15-
168
<route id="api_hydra_doc" path="/apidoc.{_format}">
179
<default key="_controller">api_platform.hydra.action.documentation</default>
1810
<default key="_api_respond">1</default>

src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@
55
xsi:schemaLocation="http://symfony.com/schema/routing
66
http://symfony.com/schema/routing/routing-1.0.xsd">
77

8-
<route id="api_jsonld_entrypoint" path="/{index}.{_format}">
9-
<default key="_controller">api_platform.action.entrypoint</default>
10-
<default key="_api_respond">1</default>
11-
<default key="_format">jsonld</default>
12-
<default key="index">index</default>
13-
<requirement key="index">index</requirement>
14-
</route>
15-
168
<route id="api_jsonld_context" path="/contexts/{shortName}.{_format}">
179
<default key="_controller">api_platform.jsonld.action.context</default>
1810
<default key="_api_respond">1</default>

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,16 @@ final class ApiLoader extends Loader
4040
private $resourceMetadataFactory;
4141
private $resourcePathGenerator;
4242
private $container;
43+
private $formats;
4344

44-
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathNamingStrategyInterface $resourcePathGenerator, ContainerInterface $container)
45+
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathNamingStrategyInterface $resourcePathGenerator, ContainerInterface $container, array $formats)
4546
{
4647
$this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing')));
4748
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
4849
$this->resourceMetadataFactory = $resourceMetadataFactory;
4950
$this->resourcePathGenerator = $resourcePathGenerator;
5051
$this->container = $container;
52+
$this->formats = $formats;
5153
}
5254

5355
/**
@@ -57,9 +59,7 @@ public function load($data, $type = null)
5759
{
5860
$routeCollection = new RouteCollection();
5961

60-
$routeCollection->addCollection($this->fileLoader->load('hal.xml'));
61-
$routeCollection->addCollection($this->fileLoader->load('jsonld.xml'));
62-
$routeCollection->addCollection($this->fileLoader->load('hydra.xml'));
62+
$this->loadExternalFiles($routeCollection);
6363

6464
if ($this->container->getParameter('api_platform.enable_swagger')) {
6565
$routeCollection->addCollection($this->fileLoader->load('swagger.xml'));
@@ -92,6 +92,21 @@ public function supports($resource, $type = null)
9292
return 'api_platform' === $type;
9393
}
9494

95+
/**
96+
* Load external files.
97+
*
98+
* @param RouteCollection $routeCollection
99+
*/
100+
private function loadExternalFiles(RouteCollection $routeCollection)
101+
{
102+
$routeCollection->addCollection($this->fileLoader->load('api.xml'));
103+
104+
if (isset($this->formats['jsonld'])) {
105+
$routeCollection->addCollection($this->fileLoader->load('jsonld.xml'));
106+
$routeCollection->addCollection($this->fileLoader->load('hydra.xml'));
107+
}
108+
}
109+
95110
/**
96111
* Creates and adds a route for the given operation to the route collection.
97112
*

src/EventListener/SerializeListener.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use ApiPlatform\Core\Exception\RuntimeException;
1515
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
1616
use ApiPlatform\Core\Util\RequestAttributesExtractor;
17+
use Symfony\Component\HttpFoundation\Request;
1718
use Symfony\Component\HttpFoundation\Response;
1819
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
1920
use Symfony\Component\Serializer\Encoder\EncoderInterface;
@@ -49,17 +50,11 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
4950
return;
5051
}
5152

52-
if ($request->attributes->get('_api_respond') && !is_object($controllerResult)) {
53-
if (!$this->serializer instanceof EncoderInterface) {
54-
throw new RuntimeException('The serializer instance must implements the "%s" interface.', EncoderInterface::class);
55-
}
56-
57-
$event->setControllerResult($this->serializer->encode($controllerResult, $request->getRequestFormat()));
58-
}
59-
6053
try {
6154
$attributes = RequestAttributesExtractor::extractAttributes($request);
6255
} catch (RuntimeException $e) {
56+
$this->serializeRawData($event, $request, $controllerResult);
57+
6358
return;
6459
}
6560

@@ -68,4 +63,30 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
6863

6964
$event->setControllerResult($this->serializer->serialize($controllerResult, $request->getRequestFormat(), $context));
7065
}
66+
67+
/**
68+
* Tries to serialize data that are not API resources (e.g. the entrypoint or data returned by a custom controller).
69+
*
70+
* @param GetResponseForControllerResultEvent $event
71+
* @param Request $request
72+
* @param object $controllerResult
73+
*/
74+
private function serializeRawData(GetResponseForControllerResultEvent $event, Request $request, $controllerResult)
75+
{
76+
if (!$request->attributes->get('_api_respond')) {
77+
return;
78+
}
79+
80+
if (is_object($controllerResult)) {
81+
$event->setControllerResult($this->serializer->serialize($controllerResult, $request->getRequestFormat()));
82+
83+
return;
84+
}
85+
86+
if (!$this->serializer instanceof EncoderInterface) {
87+
throw new RuntimeException('The serializer instance must implements the "%s" interface.', EncoderInterface::class);
88+
}
89+
90+
$event->setControllerResult($this->serializer->encode($controllerResult, $request->getRequestFormat()));
91+
}
7192
}

0 commit comments

Comments
 (0)