Global navigation

   Documentation Center
   eZ Studio & eZ Platform
     User Manual
     Technical Manual
     Glossary
   eZ Publish 4.x / legacy

 
eZ Publish (5.x)

eZ Publish 5.x | For eZ Platform & eZ Studio topics see Technical manual and User manual, for eZ Publish 4.x and Legacy topics see eZ Publish legacy

Skip to end of metadata
Go to start of metadata

 

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

While most of what can be found here will still apply in the future, the structure of the REST API and its components will evolve before the actual release.

Requirements

As of version 2013.7 / 5.2, REST routes are required to use the eZ Publish 5 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 returns 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

Route

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

ezpublish/config/routing.yml

Using a distinct file for REST routes allows you to use the prefix for all 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 controller as a service since our controller was defined as such.

My/Bundle/RestBundle/Resources/config/routing_rest.yml

Due to EZP-23016 - Custom REST API routes (v2) are not accessible from the legacy backend Closed , custom REST routes must be prefixed with ezpublish_rest_, or they won't be detected correctly.

Controller action

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

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

My/Bundle/RestBundle/Rest/Values/Hello.php

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

My/Bundle/RestBundle/Rest/Controller/DefaultController.php

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

ValueObjectVisitor

A ValueObjectVisitor will take a Value returned by a REST controller, whatever the class, and will transform it into data that can be converted, either to json or XML. Those visitors are registered as services, and tagged 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

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

  • $visitor: The output visitor. Can be used to set custom response 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 from the controller 
My/Bundle/RestBundle/Rest/Controller/Default.php

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

Cache handling

The easiest way to handle cache is to re-use the CachedValue Value Object. It acts as a proxy, 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 CachedValue, 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:

Input parser

What we have seen above covers requests that don't require an input payload, such as GET or DELETE. If you need to provide your controller with 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 Content-Type header. For example, a request with a Content-Type of application/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 create 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 format (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

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

My/Bundle/RestBundle/Resources/config/services.yml

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\Server\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 instance of our Value\Hello class.

My/Bundle/RestBundle/Rest/InputParser/Greetings.php
My/Bundle/RestBundle/Resources/config/services.yml

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

10 Comments

  1. On the "ValueObjectVisitor" topic (second code example):

    My/Bundle/RestBundle/Rest/Controller/Default.php

     

    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 )
        {
            $this->generator->startValueElement( 'Hello', $data->name );
            $this->generator->endValueElement( 'Hello' );
        }
    }

    Is the file path correct?

  2. Moreover $this->generator is wrong.. you have to use directly $generator->. But got an error: 

    So i replace code via 

    and it works..

    Maybe to complete this tutorial, if you would like to generate your result with json, you just have to pass to header Accept: application/vnd.ez.api.Test+json

    Got an other issue; your controller is wrong (smile) If you create on service

    you have to create your DefaultController.php which contains

    Promise, i'm done for now (wink)

  3. One more thing: when adding your rest routes, in ezpublish/config/routing.yml, it is important to put them ABOVE the routes form the EzPublishRestBundle, as those ones contain a last "catchall" route which will prevent the new ones to be triggered

  4. If you want a custom REST route to be recognized from the legacy backend, it MUST be prefixed with ezpublish_rest_, so, in your routing_rest.yml, this will not work:

    My/Bundle/RestBundle/Resources/config/routing_rest.yml

    but this will:

    My/Bundle/RestBundle/Resources/config/routing_rest.yml
  5. Jérôme Gamez there is a parameter you can use for that:

    legacy_aware_routes
  6. Gaetano Giunta : What legacy_aware_routes have to do with REST ?

  7. Jérôme Gamez AFAICT, you need above all to use the right prefix:

    Besides, you should use path instead of pattern to be forward compatible with Symfony 3.0 (wink)

  8. Gaetano Giunta: Sadly, setting the legacy_aware_routes parameter didn't work for me.

    Jérôme Vieilledent: I will exchange pattern with path, thank you for the hint! I have imported the routes like you said, and stated in the docs above, but still no luck. Only prefixing my distinct routes with ezpublish_rest_ worked so far to make them available for the legacy backend. For the non-legacy environment, everything worked fine without the "special" route names.

  9. Ah my bad, sorry Gaetano Giunta... Using legacy_aware_routes should indeed work. I think an issue should be reported (wink)

  10. If your Content-Type always return  text/html; charset=UTF-8, you can fix this by changing the content-type in the visitor function.

    For example with Philippe Vincent-Royol sample code :

    An XML request will return : Content-Type:  text/xml; charset=UTF-8

    A Json request will return : Content-Type:  application/json

    Thanks to Tony Collin for this hint.