eZ Community » Blogs » Sebastiaan van der Vliet » How to implement a REST-based web...

By

S V

How to implement a REST-based web service with a custom XML scheme

Sunday 22 April 2012 4:08:26 pm

  • Currently 5 out of 5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

In this blog post I will explain how to implement a REST-based web service with a custom XML scheme in eZ Publish. The setup is somewhat experimental: the REST functionality has only recently been introduced in eZ Publish, and still changes frequently. Moreover, the documentation available at this point is very limited or outdated already. Any feedback on this blog post is more than welcome!

eZ Publish provides a REST web service in order to support the development of mobile applications, or other applications. The REST API is built on top of MVCTools from eZ/Zeta Components. By default, the output type of the REST calls is JSON-data. There are a number of standard services available, for example the ‘content/node’ service. After the REST-based web service in eZ Publish has been enabled, you can access this service by going to the following URL:

 http://[www.yourdomainname.com]/api/ezp/v1/content/node/<nodeID>

It is also possible to output Atom XML:

 http://[www.yourdomainname.com]/api/ezp/v1/content/node/<nodeID>/listAtom

However, the Atom implementation is still buggy: you might have to modify ‘kernel/private/rest/classes/controllers/atom.php’ and change the following line:

 'nodeUrl' => $baseUri . $this->getRouter()->generateUrl( 1, array( 'nodeId' => $node->locations->node_id ) ) );

into:

 'nodeUrl' => $baseUri . $this->getRouter()->generateUrl( 'ezpListAtom', array( 'nodeId' => $node->locations->node_id ) ) );

Rather than using JSON or AtomXML, you might want to output content/data according to a custom XML scheme (see also the forum topic: 'REST: XML output'). Below I will explain how to accomplish this. The code is partly based on the tutorial ‘Extending eZ Publish’s REST API - Developer Preview #2’ by Łukasz Serwatka and Ole Marius Smestad. The example below has been tested for eZ Publish 4.5 and 4.6.

1. Installation of the REST-based web service in eZ Publish
First, check if your Apache rewrite rules include the line below. If not, add it:

 RewriteRule ^/api/ /index_rest\.php [L]

2. Create a new extension
In the extension folder, create a folder with the name ‘ezxrestapidemo’. Within the folder ‘ezxrestapidemo’, create three subfolders: ‘settings’, 'views' and ‘classes’. Enable the extension in site.ini.append.php:

 ActiveExtensions[]=ezxrestapidemo

3. Create an override file for rest.ini
In the folder ‘extension/ezxrestapidemo/settings’, create a file called 'rest.ini.append.php'. Add the setting below to this file.  Please note that these settings are for testing/development purposes only. The settings below are insecure, and should not be used on a production server!

 <?php /*
[ApiProvider]
ProviderClass[ezx]=ezxRestApiProvider
 
[CacheSettings]
ApplicationCache=disabled
 
[Authentication]
RequireAuthentication=disabled
DefaultUserID=14
 
*/ ?>

4. Create the custom REST implementation
Create the following files within the folder 'extension/ezxrestapidemo/classes': 'rest_provider.php', 'rest_controller.php', 'view_controller.php' and 'custom_xml.php'.

A. rest_provider.php

 <?php
 
 class ezxRestApiProvider implements ezpRestProviderInterface
 {
     public function getRoutes()
     {
            //Call ezpXMLNode as follows: http://[www.yoursite.com]/api/ezx/v1/content/xml/1/109/2987
            //Call ezpXMLManifest as follows: http://[www.yoursite.com]/api/ezx/v1/content/manifest/1/109
            return array( 'ezpXMLNode'   => new ezpRestVersionedRoute( new  ezcMvcRailsRoute( '/content/xml/:publicationId/:kbId/:nodeId', 'ezxRestController', 'viewXMLContent' ), 1 ),
                          'ezpXMLManifest' => new ezpRestVersionedRoute( new  ezcMvcRailsRoute( '/content/manifest/:publicationId/:kbId', 'ezxRestController', 'viewXMLManifest' ), 1 ) );
     }
 
     public function getViewController()
     {
             return new ezxRestApiViewController();
     }
 }
 
 ?>

You can rename rename the terms 'ezpXMLNode' and 'ezpXMLManifest' to whatever is appropriate for your project. Also, change the paths ('/content/xml/' and 'content/manifest/') to whatever you require. The terms ':publicationId', 'kbId' and ':nodeId' indicate parameters. You can rename these parameters, remove them or add more. Don't forget to add the colon in front of the parameter. Finally, you can also rename the terms 'viewXMLContent' and 'viewXMLManifest'. The comments in the file show how to call the specified routes. You can add as many routes to the array as required.

B. rest_controller.php

 <?php
 
class ezxRestController extends ezcMvcController
{
    public function doViewXMLContent()
    {
        $res = new ezpRestMvcResult();
        $res->variables['type'] = "node";
        return $res;
    }
 
    public function doViewXMLManifest()
    {
        $res = new ezcMvcResult();
        $res->variables['type'] = "manifest";
        return $res;
        }
}
 
?>

In the file 'rest_controller.php' functions are specified for the terms 'viewXMLContent' and 'viewXMLManifest'. The function name consists of "do" plus the name of the term (first character uppercase). A variable 'type' is defined that specifies the type of call. You can rename this variable or add additional variables.

C. view_controller.php

 <?php
 
class ezxRestApiViewController implements ezpRestViewControllerInterface
 
{
    public function loadView( ezcMvcRoutingInformation $routeInfo, ezcMvcRequest $request, ezcMvcResult $result )
   {
        return new ezpRestcustomXMLView( $request, $result );
   }
 
}
 
?>

The only thing noteworthy here is the name of the view: 'ezpRestcustomXMLView'. You can rename this to whatever you want, but make sure to use the same name in the file that specifies the extension of the ezcMvcView (see below).

D. custom_xml.php

 <?php
 
class ezpRestcustomXMLView extends ezcMvcView
{
    public function __construct( ezcMvcRequest $request, ezcMvcResult $result )
    {
        parent::__construct( $request, $result );
        $result->content = new ezcMvcResultContent();
        $result->content->type = "application/xml";
        $result->content->charset = "UTF-8";
 
        if (isset( $request->variables['publicationId'] ))
        $result->variables['publicationId']= $request->variables['publicationId'];
 
        if (isset( $request->variables['kbId'] ))
        $result->variables['kbID'] = $request->variables['kbId'];
 
        if (isset( $request->variables['nodeId'] ))
            $result->variables['nodeID'] = $request->variables['nodeId'];
 
        $this->type = $result->variables['type'];
    }
 
    public function createZones( $layout )
    {
        $zones = array();
        if ( $this->type=="node" ) 
            $zones[] = new ezcMvcPhpViewHandler( 'node_view', 'extension/ezxrestapidemo/views/display.php' ); 
        
        if ( $this->type=="manifest" ) 
            $zones[] = new ezcMvcPhpViewHandler( 'node_view', 'extension/ezxrestapidemo/views/manifest.php' ); 
        return $zones;
    }
}
?>

In the function _construct the request variables defined in the file rest_provider.php (nodeId, kbID and publicationId) are set so these will be available in the php files (display.php and manifest.php) called in the function 'createZones'. In addition '$this->type' is set, so the function 'createZones' can switch between the php files display.php and manifest.php. You can rename the files to something more appropriate for your project, and/or specify your own folders where the php files should be stored. Note how the function 'createZones' uses the class 'ezcMvcPhpViewHandler', which allows you to use PHP files to generate REST output.

Now regenerate the autoload file:

 php bin/php/ezpgenerateautoloads.php --extension

5. Create the PHP files that generate the REST output
Within the folder 'extension/ezxrestapidemo/views', create the files 'display.php' and 'manifest.php'. The file 'display.php' will demonstrate how to generate XML output using template files, the file 'manifest.php' will show how to generate XML output from a PHP file.

A. display.php (method using contentmodule->run)

 <?php
 
$ViewMode = "full";
$tpl = eZTemplate::factory();
 
$moduleRepositories = eZModule::activeModuleRepositories();
eZModule::setGlobalPathList( $moduleRepositories );
$contentModule = eZModule::findModule( 'content' );
 
$kbNodeID = $this->variables['kbID'];
$NodeID = $this->variables['nodeID'];
$Node = $contentModule->run(
    'view',
    array ( $ViewMode, $NodeID ),
    array(
        'ViewCache' => false,
        'AttributeValidation' => array( 'processed' => true, 'attributes' => false ),
        'CollectionAttributes' => false
    ),
    array( 'kb'=> $kbNodeID )
);
 
print $Node['content'].">";
 
?>

Notice how the variables 'kbID' and 'nodeID' are picked up by using '$this->variables'. The variable '$kbNodeID' is then passed as a view parameter to the content view. In this file I am using the module 'content' view to generate the node view through the templating system (see also my earlier blog post 'Fetching parsed template output through PHP'). Somehow the last ">" is stripped off when printing the node content in 4.5, therefore I have added it at the end, using .">" (this seems to have been fixed in eZ Publish version 4.6).

This approach is only possible if your design template files generate XML rather than HTML. One way to achieve this is by adding the following code to site.ini.append.php:

 [HTTPHeaderSettings]
CustomHeader=enabled
HeaderList[]=Content-type
Content-type[/]
Content-type[/]=text/xml

Your pagelayout.tpl file will have to look like this:

 <?xml version="1.0" encoding="UTF-8"?>
{$module_result.content}

And the full template ([design]/override/templates/full/article.tpl) to display a content class could then look something like this:

<Article>
<Title>{$node.name}</Title>
</Article>
etc.

 

B. manifest.php (method using php only)

Alternatively, it is also possible to use a function to fetch the node (e.g.: eZContentObjectTreeNode::fetch( $kbNodeID , false );) and then use the PHP code to rewrite the content of the node to XML.

<?php
 
$kbNodeID = $this->variables['kbID'];
$node = eZContentObjectTreeNode::fetch( $kbNodeID, false );
 
if ($node)
{
    $node_name = $node->attribute( 'name' );
    $node_id = $node->attribute( 'node_id' );
    $data_map = $node->attribute( 'data_map' );
}
 
print "<Article>";
print "<Title>".$node_name."</Title>";
print "</Article>";
//etc.
 
?>

6. Conclusion
Again, given the lack of documentation this approach is somewhat experimental. There might be security issues that I am not aware of. Any feedback about this approach or possible alternative ways to output custom XML through the REST service are more than welcome! For a better understanding on how to access the REST services in eZ Publish, check out the "eZ Publish REST interface developer preview example". 

Proudly Developed with from