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
The eZ Platform REST API comes with a framework that makes it quite easy= to extend the API for your own needs.
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
ValueObjectVisitor
(if your Controller retur=
ns an object that doesn't already have a converter)InputParser
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.
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
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.
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.
myRestBundle_hello_world: pattern: /my_rest_bundle/hello/{name} defaults: _controller: myRestBundle.controller.default:sayHello methods: [GET]=20
Due to EZP-23016<=
/a> - Custom REST API routes (v2) are not accessibl=
e from the legacy backend=20
Closed , custom R=
EST routes must be prefixed with
ezpublish_rest_
, or they won'=
t be detected correctly.
Unlike standard Symfony 2 controllers, the REST ones don't return an 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
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.
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.
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.
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 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.
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:
return new CachedValue($helloValue, ['locationId', 42]);=20
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:
<?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.
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.
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
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.
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:
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"/>