- Documentation >
- API >
- REST API guide >
- Accept header-based REST API response
Customized REST API response can be used in many situations, both for headless and more traditional setups. REST responses can be enriched in a clean way and limit client-to-server round trips.
To do this you can take advantage of eZ Platform's HATEOAS-based REST API and extend it with custom Content Types for your own needs. In this procedure you will add comments count to eZ\Publish\API\Repository\Values\Content\VersionInfo
responses.
Implementation of dedicated Visitor
The first step is creating your own implementation of ValueObjectVisitor
. It contains all the logic responsible for:
- fetching data you want to present
- modifying the actual response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | <?php
namespace AppBundle\Rest\ValueObjectVisitor;
use eZ\Publish\API\Repository\Repository;
use eZ\Publish\Core\REST\Common\Output\Generator;
use eZ\Publish\Core\REST\Common\Output\Visitor;
use eZ\Publish\Core\REST\Server\Output\ValueObjectVisitor\VersionInfo as BaseVersionInfo;
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
class VersionInfo extends BaseVersionInfo
{
private $repository;
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
protected function visitVersionInfoAttributes(Visitor $visitor, Generator $generator, APIVersionInfo $versionInfo)
{
parent::visitVersionInfoAttributes($visitor, $generator, $versionInfo);
$this->visitCommentValue($generator, $versionInfo);
}
protected function visitCommentValue(Generator $generator, APIVersionInfo $versionInfo)
{
$generator->startValueElement('commentsCount', $this->loadCommentsCount($versionInfo));
$generator->endValueElement('commentsCount');
}
private function loadCommentsCount(APIVersionInfo $versionInfo)
{
// load comments count using the repository (injected), or any comments backend
}
}
|
Overriding response type
Next, make sure that your new implementation of serialization applies only to the selected objects. In order to do that, you need to
decorate eZ\Publish\Core\REST\Common\Output\ValueObjectVisitorDispatcher
from ezpublish-kernel
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | <?php
namespace AppBundle\Rest;
use eZ\Publish\Core\REST\Common\Output\Generator;
use eZ\Publish\Core\REST\Common\Output\ValueObjectVisitorDispatcher as BaseValueObjectVisitorDispatcher;
use eZ\Publish\Core\REST\Common\Output\Exceptions\NoVisitorFoundException;
use eZ\Publish\Core\REST\Common\Output\Visitor;
class ValueObjectVisitorDispatcher extends BaseValueObjectVisitorDispatcher
{
private $parentDispatcher;
public function __construct(BaseValueObjectVisitorDispatcher $parentDispatcher)
{
$this->parentDispatcher = $parentDispatcher;
}
public function setOutputVisitor(Visitor $outputVisitor)
{
parent::setOutputVisitor($outputVisitor);
$this->parentDispatcher->setOutputVisitor($outputVisitor);
}
public function setOutputGenerator(Generator $outputGenerator)
{
parent::setOutputGenerator($outputGenerator);
$this->parentDispatcher->setOutputGenerator($outputGenerator);
}
public function visit($data)
{
try {
return parent::visit($data);
} catch (NoVisitorFoundException $e) {
return $this->parentDispatcher->visit($data);
}
}
}
|
To be able to use the overridden type you also need to implement new Compiler Pass. For more details, see Symfony doc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | <?php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class ValueObjectVisitorPass implements CompilerPassInterface
{
const TAG_NAME = 'app.value_object_visitor';
const DISPATCHER_DEFINITION_ID = 'app.rest.output.value_object_visitor.dispatcher';
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition(self::DISPATCHER_DEFINITION_ID)) {
return;
}
$definition = $container->getDefinition(self::DISPATCHER_DEFINITION_ID);
foreach ($container->findTaggedServiceIds(self::TAG_NAME) as $id => $attributes) {
foreach ($attributes as $attribute) {
if (!isset($attribute['type'])) {
throw new \LogicException(self::TAG_NAME . ' service tag needs a "type" attribute to identify the field type. None given.');
}
$definition->addMethodCall(
'addVisitor',
[$attribute['type'], new Reference($id)]
);
}
}
}
}
|
Also, do not forget to register it in your bundle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | <?php
namespace AppBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use AppBundle\DependencyInjection\Compiler\ValueObjectVisitorPass;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ValueObjectVisitorPass());
}
}
|
Configuration
The last thing you need to do is to set a configuration which should be located in the services.yml
file of your bundle.
The important part are the keys:
app.rest.output.visitor.json.regexps
which helps identifying proper header
priority
which should be set high enough, to not be overridden by another implementation
All the other keys need to correspond with the current namespace of your bundle. In this example it is just AppBundle
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | parameters:
app.rest.output.visitor.json.regexps:
- '(^application/my\.api\.[A-Za-z]+\+json$)'
app.rest.generator.json.vendor: 'my.api'
services:
app.rest.output.generator.json:
class: eZ\Publish\Core\REST\Common\Output\Generator\Json
arguments:
- '@ezpublish_rest.output.generator.json.field_type_hash_generator'
- '%app.rest.generator.json.vendor%'
calls:
- [ setFormatOutput, [ '%kernel.debug%' ] ]
app.rest.output.visitor.json:
class: '%ezpublish_rest.output.visitor.class%'
arguments:
- '@app.rest.output.generator.json'
- '@app.rest.output.value_object_visitor.dispatcher'
tags:
- { name: ezpublish_rest.output.visitor, regexps: app.rest.output.visitor.json.regexps, priority: 200 }
app.rest.output.value_object_visitor.dispatcher:
class: AppBundle\Rest\ValueObjectVisitorDispatcher
arguments:
- '@ezpublish_rest.output.value_object_visitor.dispatcher'
app.rest.output.value_object_visitor.version_info:
class: AppBundle\Rest\ValueObjectVisitor\VersionInfo
parent: ezpublish_rest.output.value_object_visitor.base
arguments:
- '@ezpublish.api.repository'
tags:
- { name: app.value_object_visitor, type: eZ\Publish\API\Repository\Values\Content\VersionInfo }
|
Fetching the modified response
After following all the steps you should see an example of the modified API response below. As you see media-type
is correctly interpreted and commentsCount
is also appended (it's null
as you did not provide any logic to fetch it).
Please note that you should set a proper Accept
header value. For this example: application/my.api.VersionList+json
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | {
"VersionList": {
"_media-type": "application\/my.api.VersionList+json",
"_href": "\/api\/ezp\/v2\/content\/objects\/1\/versions",
"VersionItem": [
{
"Version": {
"_media-type": "application\/my.api.Version+json",
"_href": "\/api\/ezp\/v2\/content\/objects\/1\/versions\/9"
},
"VersionInfo": {
"id": 506,
"versionNo": 9,
"status": "PUBLISHED",
"modificationDate": "2015-11-30T14:10:46+01:00",
"Creator": {
"_media-type": "application\/my.api.User+json",
"_href": "\/api\/ezp\/v2\/user\/users\/14"
},
"creationDate": "2015-11-30T14:10:45+01:00",
"initialLanguageCode": "eng-GB",
"languageCodes": "eng-GB",
"VersionTranslationInfo": {
"_media-type": "application\/my.api.VersionTranslationInfo+json",
"Language": [
{
"languageCode": "eng-GB"
}
]
},
"names": {
"value": [
{
"_languageCode": "eng-GB",
"#text": "eZ Platform"
}
]
},
"Content": {
"_media-type": "application\/my.api.ContentInfo+json",
"_href": "\/api\/ezp\/v2\/content\/objects\/1"
},
"commentsCount": null
}
}
]
}
}
|
Tip
You can test your response by using JavaScript/AJAX example code, see testing the API.