Learn / eZ Publish / Extending eZ Publish’s REST API - Developer Preview #2

Extending eZ Publish’s REST API - Developer Preview #2

This lets you expose any service reading content (heading for a more complete CRUD support eventually ), as well as other, fully custom features, with direct access to about any eZ Publish feature, from PHP.

This tutorial is based on the second developer preview of eZ Publish’s REST framework, still being evolved to reach its stable state around the Matterhorn (4.5) release. There may be a few rough edges. Authentication (OAuth) is supported, but was disabled in this 2nd developer preview.

 

Pre-requisites and target population

This tutorial is for developers who are keen on making their eZ Publish a REST engine for their specific needs. This 2nd developer preview follows a first one, with which we recommend you to quickly get acquainted. Developer preview means that what you will learn in a few moments is necessarily going to evolve, change, maybe not only slightly. This is not a crystallised piece of knowledge, rather a intermediate (yet pretty advanced) deliverable in an iterative process. Let us all be agile !

Important preliminary note:

We are highly recommending to setup REST layer in Virtual Host environment for this release. We are working on adding support for other environments as well. Stay tuned!

Read more on how to set-up your eZ Publish in Virtual Host mode : http://doc.ez.no/eZ-Publish/Technical-manual/4.4/Installation/Virtual-host-setup

 

Step 1 : Installation

Here are the extensions/tarballs you will build off of :

Unpack the first one somewhere, and follow the instructions in the INSTALL file, found at the root of what you just unpacked.

The second tarball above is actually what you should end-up with at the end of this tutorial. We recommend you build it yourself, following the steps below, much better to understand what you are doing. If you are in a super-rush though, 25 minutes away from the Christmas diner (random example), you may want to download it :)

 

Step 2 : Understanding the REST URI pattern

The resource URIs provided by default by the eZ Publish REST extension support the following convention:

/<prefix>/ + <provider>/<version> + /<function>/<params>

For the built-in REST API, the default provider name is “ezp”. The version token is build as “v + integer” for example v1 for REST API version one. The global prefix (first URI part) can be defined in the REST configuration as settings/rest.ini.[System].ApiPrefix.

The RegExp based URI filtering is handled by a default “ezpRestDefaultRegexpPrefixFilter” class implementation where <provider> and <version> data are used for routs filtering. Developers can implement custom filters by implementing the “ezpRestPrefixFilterInterface” in first place and then updating the “rest.ini.[System].PrefixFilterClass” accordingly. Getting implementation ideas out of the “ezpRestDefaultRegexpPrefixFilter” can be a good start.

 

Step 3 : Understanding REST API versioning

The resource URIs contain a version token which is build as “v + integer”, for example v1 for REST API version one, v2 for version two and so on. Based on the version information provided in the resource URI the pre-routing filter extracts version information and matches against defined versioned routes. The “ezpRestVersionedRoute” is a route wrapper around existing instance of “ezcMvcRoute” object providing multiple versions of it. Find an example of a versioned routes definition below:

 

URI: /api/provider/v1/foo

new ezpRestVersionedRoute( new ezcMvcRailsRoute( '/foo', 'ezxRestController', 'foo' ), 1 )

URI: /api/provider/v2/foo

new ezpRestVersionedRoute( new ezcMvcRailsRoute( '/foo', 'ezxRestController', 'fooBar' ), 2 )
 

For the first URI ezxRestController::doFoo() method will be executed, which is version one. For the second URI which is a version two, ezxRestController::doFooBar()will be called. Both methods can share implementation logic but differ with the output result for instance.

 

Step 4 : Extending REST

As of now it is possible to extend the eZ Publish REST interface with custom functionality. One REST extension can support many different data providers as well as different versions. The following steps presents how to create a new eZ Publish REST extension.

1. Create an empty folder called “ezxrestapidemo” under the “ <eZ Publish>/extension” location.

2. Inside “ezxrestapidemo” create a setting with the “rest.ini.append.php” file with following content:

<?php /*

[ApiProvider]
ProviderClass[ezx]=ezxRestApiProvider

*/ ?>

ProviderClass is an array where the index is a provider token used in the resource URI. For example for URI: “/api/ezx/v1/foo” the registered provider with token “ezx” will be called. You can have many providers registered within one extension.

 

3. Next create a folder called “classes” under the “<eZ Publish>/extension/ezxrestapidemo/” location with the “rest_provider.php file”. Edit the newly created PHP file and add the following code:

<?php
class ezxRestApiProvider implements ezpRestProviderInterface
{
    /**
     * Returns registered versioned routes for provider
     *
     * @return array
     */
    public function getRoutes()
    {
        return array( new ezpRestVersionedRoute( new ezcMvcRailsRoute( '/foo',
                                                          'ezxRestController', 'foo' ), 1 ),                                                         
                      new ezpRestVersionedRoute( new ezcMvcRailsRoute( '/foo',                        
                                                           'ezxRestController', 'fooBar' ), 2 ) );
    }

    /**
     * Returns associated with provider view controller
     *
     * @return ezpRestViewController
     */
    public function getViewController()
    {
        return new ezxRestApiViewController();
    }
}
?>

Every REST API provider has to implement the “ezpRestProviderInterface” where “getRoutes()” methods returns registered versioned routes for provider and “getViewController()” returns view controller object which has to implement the “ezpRestViewControllerInterface”.

 

4. In the next step create the file “view_controller.php” under the “<eZ Publish>/extension/ezxrestapidemo/classes” folder with following code:

<?php
class ezxRestApiViewController implements ezpRestViewControllerInterface
{
    /**
     * Creates a view required by controller's result
     *
     * @param ezcMvcRoutingInformation $routeInfo

    * @param ezcMvcRequest $request
    * @param ezcMvcResult $result
    * @return ezcMvcView
    */
    public function loadView( ezcMvcRoutingInformation $routeInfo, ezcMvcRequest $request,
ezcMvcResult $result )
    {
        return new ezpRestJsonView( $request, $result );
    }
}
?>

Every view controller has to implement the “ezpRestViewControllerInterface” where the “loadView()” method returns an instance of the “ezcMvcView” object. In this example we re-use the “ezpRestJsonView” which is a part of the built-in API, if your provider need a custom view, you can return it here.

 

5. Registered for REST provider URIs are associated with controllers. This example calls the methods foo() and fooBar() on “ezxRestController”. Create a file called “rest_controller.php” under “<eZ Publish>/extension/ezxrestapidemo/classes” folder and put the following code inside:

<?php

class ezxRestController extends ezcMvcController
{
    public function doFoo()
    {
        $res = new ezcMvcResult();
        $res->variables['message'] = "This is FOO!";
        return $res;
    }

    public function doFooBar()
    {
        $res = new ezcMvcResult();
        $res->variables['message'] = "This is FOOBAR!";
        return $res;
    }
}

?>

Notice the controller methods naming convention. All methods have to be prefixed with “do” word. This is a “ezcMvcController” requirement.

 

6. Enable “ezxrestapidemo” by adding the following to “ActiveExtensions” list in the “settings/override/site.ini.append.php”

[ExtensionSettings]
[…]
ActiveExtensions[]=oauth
ActiveExtensions[]=rest
ActiveExtensions[]=ezprestapiprovider
ActiveExtensions[]=ezxrestapidemo
 

7. Update the autoload array with a following command from the eZ Publish root folder :

php bin/php/ezpgenerateautoloads.php -e -p
 

8. If you have followed all steps carefully then you should be ready to test your new REST API provider by calling www.example.com/api/ezx/v1/foo A simple JSON response should look like :

{"message":"This is FOO!"}

For the second URI www.example.com/api/ezx/v2/foo output should be following:

{"message":"This is FOOBAR!"}
 

Conclusion

You should now have the ezxrestapidemo extension ready, exposing your custom REST api to any REST-enabled channel. This tutorial showed the principle of extending the eZ Publish REST framework. Given the “preview” nature of this framework, some changes might occur in the future on the way REST extensions are done, stay tuned for all details. However, the foundations are now laid, let us build off of them together. You can now already start using it for leveraging the multi-channel approach. A few links in the Resources part to get you started building mobile apps.

Please share your feedback, impressions, ideas as comments under this post, that is how we move on together!

 

Resources

This article is available for offline reading in PDF format :
Extending eZ Publish’s REST API - Developer Preview #2 - PDF Version

 

About the authors : Łukasz & Ole Marius

Łukasz Serwatka is a Software Engineer at eZ Systems, focusing on dedicated eZ Publish solutions such as eZ Flow and the Website Interface.
http://serwatka.net

Ole Marius joined eZ Systems in 2005. He holds a master of technology from the the Norwegian University of Science and Technology. He is currently serving as domain leader for eZ Publish.

 

License choice

This work is licensed under the Creative Commons – Share Alike license (http://creativecommons.org/licenses/by-sa/3. ).

eZ debug

Timing: Jan 17 2025 17:54:51
Script start
Timing: Jan 17 2025 17:54:51
Module start 'content'
Timing: Jan 17 2025 17:54:51
Module end 'content'
Timing: Jan 17 2025 17:54:51
Script end

Main resources:

Total runtime0.2395 sec
Peak memory usage4,096.0000 KB
Database Queries206

Timing points:

CheckpointStart (sec)Duration (sec)Memory at start (KB)Memory used (KB)
Script start 0.00000.0060 596.1953180.8203
Module start 'content' 0.00600.1472 777.0156936.4141
Module end 'content' 0.15320.0862 1,713.4297338.0078
Script end 0.2393  2,051.4375 

Time accumulators:

 Accumulator Duration (sec) Duration (%) Count Average (sec)
Ini load
Load cache0.00371.5302210.0002
Check MTime0.00140.5686210.0001
Mysql Total
Database connection0.00080.313110.0008
Mysqli_queries0.114747.87392060.0006
Looping result0.00130.54792040.0000
Template Total0.216890.520.1084
Template load0.00200.822420.0010
Template processing0.214889.686320.1074
Template load and register function0.00010.044710.0001
states
state_id_array0.00321.342860.0005
state_identifier_array0.00190.797370.0003
Override
Cache load0.00301.25011510.0000
Sytem overhead
Fetch class attribute name0.00170.689250.0003
Fetch class attribute can translate value0.00040.158250.0001
class_abstraction
Instantiating content class attribute0.00000.007650.0000
XML
Image XML parsing0.00582.426950.0012
General
dbfile0.01064.4434370.0003
String conversion0.00000.002630.0000
Note: percentages do not add up to 100% because some accumulators overlap

CSS/JS files loaded with "ezjscPacker" during request:

CacheTypePacklevelSourceFiles
CSS0extension/community/design/community/stylesheets/ext/jquery.autocomplete.css
extension/community_design/design/suncana/stylesheets/scrollbars.css
extension/community_design/design/suncana/stylesheets/tabs.css
extension/community_design/design/suncana/stylesheets/roadmap.css
extension/community_design/design/suncana/stylesheets/content.css
extension/community_design/design/suncana/stylesheets/star-rating.css
extension/community_design/design/suncana/stylesheets/syntax_and_custom_tags.css
extension/community_design/design/suncana/stylesheets/buttons.css
extension/community_design/design/suncana/stylesheets/tweetbox.css
extension/community_design/design/suncana/stylesheets/jquery.fancybox-1.3.4.css
extension/bcsmoothgallery/design/standard/stylesheets/magnific-popup.css
extension/sevenx/design/simple/stylesheets/star_rating.css
extension/sevenx/design/simple/stylesheets/libs/fontawesome/css/all.min.css
extension/sevenx/design/simple/stylesheets/main.v02.css
extension/sevenx/design/simple/stylesheets/main.v02.res.css
JS0extension/ezjscore/design/standard/lib/yui/3.17.2/build/yui/yui-min.js
extension/ezjscore/design/standard/javascript/jquery-3.7.0.min.js
extension/community_design/design/suncana/javascript/jquery.ui.core.min.js
extension/community_design/design/suncana/javascript/jquery.ui.widget.min.js
extension/community_design/design/suncana/javascript/jquery.easing.1.3.js
extension/community_design/design/suncana/javascript/jquery.ui.tabs.js
extension/community_design/design/suncana/javascript/jquery.hoverIntent.min.js
extension/community_design/design/suncana/javascript/jquery.popmenu.js
extension/community_design/design/suncana/javascript/jScrollPane.js
extension/community_design/design/suncana/javascript/jquery.mousewheel.js
extension/community_design/design/suncana/javascript/jquery.cycle.all.js
extension/sevenx/design/simple/javascript/jquery.scrollTo.js
extension/community_design/design/suncana/javascript/jquery.cookie.js
extension/community_design/design/suncana/javascript/ezstarrating_jquery.js
extension/community_design/design/suncana/javascript/jquery.initboxes.js
extension/community_design/design/suncana/javascript/app.js
extension/community_design/design/suncana/javascript/twitterwidget.js
extension/community_design/design/suncana/javascript/community.js
extension/community_design/design/suncana/javascript/roadmap.js
extension/community_design/design/suncana/javascript/ez.js
extension/community_design/design/suncana/javascript/ezshareevents.js
extension/sevenx/design/simple/javascript/main.js

Templates used to render the page:

UsageRequested templateTemplateTemplate loadedEditOverride
1node/view/full.tplfull/article.tplextension/sevenx/design/simple/override/templates/full/article.tplEdit templateOverride template
1content/datatype/view/ezxmltext.tpl<No override>extension/community_design/design/suncana/templates/content/datatype/view/ezxmltext.tplEdit templateOverride template
11content/datatype/view/ezxmltags/link.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/link.tplEdit templateOverride template
39content/datatype/view/ezxmltags/paragraph.tpl<No override>extension/ezwebin/design/ezwebin/templates/content/datatype/view/ezxmltags/paragraph.tplEdit templateOverride template
17content/datatype/view/ezxmltags/separator.tpl<No override>extension/community_design/design/suncana/templates/content/datatype/view/ezxmltags/separator.tplEdit templateOverride template
4content/datatype/view/ezxmltags/embed.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/embed.tplEdit templateOverride template
4content/view/embed.tplembed/image.tplextension/sevenx/design/simple/override/templates/embed/image.tplEdit templateOverride template
4content/datatype/view/ezimage.tpl<No override>extension/sevenx/design/simple/templates/content/datatype/view/ezimage.tplEdit templateOverride template
9content/datatype/view/ezxmltags/header.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/header.tplEdit templateOverride template
11content/datatype/view/ezxmltags/strong.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/strong.tplEdit templateOverride template
4content/datatype/view/ezxmltags/newpage.tpl<No override>extension/community/design/standard/templates/content/datatype/view/ezxmltags/newpage.tplEdit templateOverride template
7content/datatype/view/ezxmltags/li.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/li.tplEdit templateOverride template
2content/datatype/view/ezxmltags/ul.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/ul.tplEdit templateOverride template
11content/datatype/view/ezxmltags/literal.tpl<No override>extension/community/design/standard/templates/content/datatype/view/ezxmltags/literal.tplEdit templateOverride template
2content/datatype/view/ezxmltags/line.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/line.tplEdit templateOverride template
1content/datatype/view/ezxmltags/embed-inline.tpl<No override>design/standard/templates/content/datatype/view/ezxmltags/embed-inline.tplEdit templateOverride template
1content/view/embed-inline.tpl<No override>design/standard/templates/content/view/embed-inline.tplEdit templateOverride template
1pagelayout.tpl<No override>extension/sevenx/design/simple/templates/pagelayout.tplEdit templateOverride template
 Number of times templates used: 130
 Number of unique templates used: 18

Time used to render debug report: 0.0002 secs