Message-ID: <1703625216.3748.1485855148590.JavaMail.confluence@ip-10-127-227-164> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_3747_1449044195.1485855148590" ------=_Part_3747_1449044195.1485855148590 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html Extending the REST API

Extending the REST API

=20
=20
=20
=20

The eZ Platform REST API comes with a framework that makes it quite easy= to extend the API for your own needs.

Requirements

REST routes are required to use the eZ Platform REST API prefix, /= api/ezp/v2. You can create new resources below this prefix.

To do so, you will/may need to create

  • a Controller that will handle your route actions
  • a Route, in your bundle's routing file
  • a Controller action
  • Optionally, a ValueObjectVisitor (if your Controller retur= ns an object that doesn't already have a converter)
  • Optionally, an InputParser

Controller

To create a REST controller, you need to extend the ezpublish_rest= .controller.base service, as well as the eZ\Publish\Core\REST\= Server\Controller class.

Let's create a very simple controller, that has a sayHello() method, that takes a name as an argument.

My/Bundle/RestBundle/Rest/Controller/DefaultController.php
=20
namespace My\Bundle\RestBundle\Rest\Controller;
 
use eZ\Publish\Core\REST\Server\Controller as BaseController;
 
class DefaultController extends BaseController
{
    public function sayHello( $name )
    {
        // @todo Implement me
    }
}
=20

Route

As said earlier, your REST routes are required to use the REST URI prefi= x. To do so, the easiest way is to import your routing file using this pref= ix.

app/config/routing.yml
=20
myRestBundle_rest_routes:
    resource: "@MyRestBundle/Resources/config/routing_rest.yml"
    prefix:   %ezpublish_rest.path_prefix%
=20

Using a distinct file for REST routes allows you to use the prefix for a= ll this file's routes without affecting other routes from your bundle.

Next, you need to create the REST route. We need to define the route's <= a href=3D"http://symfony.com/doc/current/cookbook/controller/service.html" = class=3D"external-link" rel=3D"nofollow">controller as a service since = our controller was defined as such.

My/Bundle/RestBundle/Resources/config/routing_rest.yml
=20
myRestBundle_hello_world:
    pattern: /my_rest_bundle/hello/{name}
    defaults:
        _controller: myRestBundle.controller.default:sayHello
    methods: [GET]
=20

Controller action

Unlike standard Symfony 2 controllers, the REST ones don't return an HttpFoundation\Response object, but a ValueObject. T= his object will during the kernel run be converted, using a ValueObjectVisi= tor, to a proper Symfony 2 response. One benefit is that when multiple cont= rollers return the same object, such as a Content item or a Location, the v= isitor will be re-used.

Let's say that our Controller will return a My\Bundle\RestBundle\R= est\Values\Hello

My/Bundle/RestBundle/Rest/Values/Hello.php
=20
namespace My\Bundle\RestBundle\Rest\Values;
 
class Hello
{
    public $name;
 
    public function __construct( $name )
    {
        $this->name =3D $name;
    }
}
=20

We will return an instance of this class from our sayHello() controller method.

My/Bundle/RestBundle/Rest/Controller/DefaultController.php
=20
namespace My\Bundle\RestBundle\Controller;

use eZ\Publish\Core\REST\Server\Controller as BaseController;
use My\Bundle\RestBundle\Rest\Values\Hello as HelloValue;

class DefaultController extends BaseController
{
    public function sayHello( $name )
    {
        return new HelloValue( $name );
    }
}
=20

And that's it. Outputting this object in the Response requires that we c= reate a ValueObjectVisitor.

ValueObjectVisitor

A ValueObjectVisitor will take a Value returned by a REST controller, wh= atever the class, and will transform it into data that can be converted, ei= ther to json or XML. Those visitors are registered as services, and ta= gged with ezpublish_rest.output.value_object_visitor. The tag = attribute says which class this Visitor applies to.

Let's create the service for our ValueObjectVisitor first.

My/Bundle/RestBundle/Resources/config/services.yml
=20
services:
    myRestBundle.value_object_visitor.hello:
        parent: ezpublish_rest.output.value_object_visitor.base
        class: My\Bundle\RestBundle\Rest\ValueObjectVisitor\Hello
        tags:
            - { name: ezpublish_rest.output.value_object_visitor, type: My\=
Bundle\RestBundle\Rest\Values\Hello }
=20

Let's create our visitor next. It must extend the  eZ\Publish= \Core\REST\Common\Output\ValueObjectVisitor abstract class, and impl= ement the visit() method.
It will receive as arguments:

  • $visitor: The output visitor. Can be used to set custom re= sponse headers ( setHeader( $name, $value )), HTTP status= code ( setStatus( $statusCode ) )...
  • $generator: The actual Response generator. It provides you= with a DOM like API.
  • $data: the visited data, the exact object you returned fro= m the controller 
My/Bundle/RestBundle/Rest/Controller/Default.php
=20
namespace My\Bundle\RestBundle\Rest\ValueObjectVisitor;

use eZ\Publish\Core\REST\Common\Output\ValueObjectVisitor;
use eZ\Publish\Core\REST\Common\Output\Generator;
use eZ\Publish\Core\REST\Common\Output\Visitor;

class Hello extends ValueObjectVisitor
{
    public function visit( Visitor $visitor, Generator $generator, $data )
    {
        $generator->startValueElement( 'Hello', $data->name );
        $generator->endValueElement( 'Hello' );
    }
}
=20

Do not hesitate to look into the built-in ValueObjectVisitors, in <= code> eZ/Publish/Core/REST/Server/Output/ValueObjectVisitor<= /a> , for more examples.

Cache handling

The easiest way to handle cache is to re-use the  CachedValue Value Object. It acts as a prox= y, and adds the cache headers, depending on the configuration, for a given = object and set of options.

When you want the response to be cached, return an instance of CachedVal= ue, with your Value Object as the argument. You can also pass a location id= using the second argument, so that the Response is tagged with it:

=20
return new CachedValue($helloValue, ['locationId', 42]);
=20

Input parser

What we have seen above covers requests that don't require an input payl= oad, such as GET or DELETE. If you need to provide your controller wit= h parameters, either in JSON or XML, the parameter struct requires an Input= Parser so that the payload can be converted to an actual ValueObject.

Each payload is dispatched to its Input Parser based on the request's Co= ntent-Type header. For example, a request with a Content-Type of appl= ication/vnd.ez.api.ContentCreate will be parsed by eZ\Publish\= Core\REST\Server\Input\Parser\ContentCreate. This parser will build = and return a ContentCreateStruct that can then be used to crea= te content with the Public API.

Those input parsers are provided with a pre-parsed version of the input = payload, as an associative array, and don't have to care about the actual f= ormat (XML or JSON).

Let's see what it would look like with a Content-Type of application/vnd= .my.Greetings, that would send this as XML:

application/vnd.my.Greetings+xml
=20
<?xml version=3D"1.0" encoding=3D"utf-8"?>
<Greetings>
    <name>John doe</name>
</Greetings>
=20

First, we need to create a service with the appropriate tag in services.= yml.

My/Bundle/RestBundle/Resources/config/services.yml
=20
services:
    myRestBundle.input_parser.Greetings:
        parent: ezpublish_rest.input.parser
        class: My\Bundle\RestBundle\Rest\InputParser\Greetings
        tags:
            - { name: ezpublish_rest.input.parser, mediaType: application/v=
nd.my.Greetings }

=20

The mediaType attribute of the ezpublish_rest.input.parser tag maps our = Content Type to the input parser.

Let's implement our parser. It must extend eZ\Publish\Core\REST\Ser= ver\Input\Parser, and implement the parse() method. It accepts= as an argument the input payload, $data, as an array, and an = instance of ParsingDispatcher that can be used to forward= parsing of embedded content.

For convenience, we will consider that our input parser returns an insta= nce of our Value\Hello class.

My/Bundle/RestBundle/Rest/InputParser/Greetings.php
=20
namespace My\Bundle\RestBundle\Rest\InputParser;
 
use eZ\Publish\Core\REST\Common\Input\BaseParser;
use eZ\Publish\Core\REST\Common\Input\ParsingDispatcher;
use My\Bundle\RestBundle\Rest\Value\Hello;
use eZ\Publish\Core\REST\Common\Exceptions;


class Greetings extends BaseParser
{
    /**
     * @return My\Bundle\RestBundle\Rest\Value\Hello
     */
    public function parse( array $data, ParsingDispatcher $parsingDispatche=
r )
=09{
        // re-using the REST exceptions will make sure that those alre=
ady have a ValueObjectVisitor
        if ( !isset( $data['name'] ) )
            throw new Exceptions\Parser( "Missing or invalid 'name' element=
 for Greetings." );


        return new Hello( $data['name'] );
=09}
}
=20
My/Bundle/RestBundle/Resources/config/services.yml
=20
services:
    myRestBundle.controller.default:
        class: My\Bundle\RestBundle\Rest\Controller\Default
        parent: ezpublish_rest.controller.base
=20

Do not hesitate to look into the built-in InputParsers, in eZ/Publ= ish/Core/REST/Server/Input/Parser, for more examples.

 

Registerin= g resources in the REST root

You can register newly added resources so that they show up in the REST = root resource for automatic discovery.

New resources can be registered with code like this:

=20
ez_publish_rest:
    system:
        default:
            rest_root_resources:
                someresource:
                    mediaType: 'Content'
                    href: 'router.generate("ezpublish_rest_loadContent", {"=
contentId": 2})'
=20

with someresource being a unique key.

 

The router.generate call dynamically renders a URI based on= the name of the route and the optional parameters that are passed as the o= ther arguments (in the code above this is the contentId).

This syntax is based on Symfony's expression language, an extensible component that allows l= imited / readable scripting to be used outside code context.

 

The above configuration will add the following entry to the root resourc= e:

<someresource media-type=3D"application/vnd.ez.api.Content+xml"= href=3D"/api/ezp/v2/content/objects/2"/>

=20
=20
=20
=20

In this topic:

=20
=20
=20
------=_Part_3747_1449044195.1485855148590--