Zend JSON-RPC with Dojo Howto

I have been using the Dojo toolkit as my Javascript library of choice since back in early 2006 when it was still around version 0.4. Since then, the project has made tremendous strides including the release of version 1.0 and 1.1 with 1.2 on the way. At the beginning of 2008 I started using the Zend Framework to build MVC PHP applications and, with the release of 1.5, it has become my PHP framework of choice.

To my delight, the Zend Framework and Dojo have recently announced a partnership which will lead to tighter integration of these two great open source frameworks.

One of the most exciting additions to the Zend Framework is the Zend_Json_Server support for JSON-RPC. I have been using JSON-RPC with Dojo for quite some time and, up until now, it has been challenging to find a design pattern that plays nice with the Model View Controller implementation in the Zend Framework. However, with the addition of the Zend_Json_Server this is no longer the case.

Prerequisites:

This tutorial assumes you are familiar with developing in PHP and javascript and are experienced with dojo and the Zend Framework using the MVC paradigm.

Common Pitfalls

  • Absolute paths are used in the code examples. Ensure that you modify them to reflect your server’s directory structure. This tutorial assumes that you are working with a ZF project whose /html/ directory corresponds to the root of your webserver
  • Ensure you place a .htaccess file in /html/scripts/ that disables the re-write engine. If you fail to do this step, when trying to load dojo libraries, ZF will be trying to find controllers that match the requests and throwing exceptions.

Patching Zend Framework

At the time of writing, the Json-RPC component has not been accepted into the Zend Framework (although it will be soon). To get the Zend Framework (ZF) working with Json-RPC we must download code from the incubator area of SVN. If you don’t know how to use a SVN client to check out code, read about it here. Download the current version of ZF and then, using an SVN client, download]4 and place it in the json directory of your local copy of ZF.

Setting up the Directories

For the purpose of this tutorial I will be using code and examples from Nickel: the application that will eventually run SFU Bookswap V.3. I use a fairly standard MVC directory structure as recommended by the ZF docs:

application
	|-default
	|-controllers
	|-views
html
	|-images
	|-scripts
		|-nickel
			|-RPC
library
	|-Nickel
		|-Models
		|-RPC
	|-Zend
		| .... Framework checkout

The reasoning behind placing the Nickel folder in the library is so we can use Zend_Loader to load classes on demand. Also, as a condition of this, the classes in the Nickel folder follow the ZF naming convention: so, say, the class for a book model would be named, Nickel_Model_Book.

Similarly, there is a corresponding directory structure in the scripts folder. This is to be used with the Dojo packaging system. So, to access the RPC handle for the book model we would call a function such as nickel.rpc.book.getAuthor()

Creating the RPC Controller

The next step is to actually create the RPC controller. I have created a file in /application/default/controllers/RpcController.php which contains the RpcController action controller class. RpcController has two actions: one which will return the SMD (simple method declaration) to dojo when the JSON-RPC object is instantiated. The second action, the service action, actually processes the RPC request and returns the result. So, we have:

#/application/default/controllers/RpcController.php
<?php
class RpcController extends Zend_Controller_Action {

    public function smdAction(){
        $class = $this->_getParam('class');

        $server = new Zend_Json_Server();

        $server->getServiceMap()->setDojoCompatible(true);
        $server->getServiceMap()->setTransport('POST')
            ->setTarget($this->getHelper('url')->url(array('controller'=>'rpc', 'action'=>'service')))
            ->setId($this->getHelper('url')->url(array('controller'=>'rpc', 'action'=>'service')));

        $server->setClass($class);

        $this->view->data = $server->getServiceMap();
        $this->render('service');
    }

    public function serviceAction(){
        $class = $this->_getParam('class');

        $server = new Zend_Json_Server();
        $server->setClass($class);
        $server->setAutoEmitResponse(true);

        $server->handle();
    }
}

The smd Action takes ‘class’ as a parameter. This is the name of the class that we wish to expose via Json-RPC, such as Nickel_Rpc_Book. Please note the function call setDojoCompatible(true) which forces the server to generate dojo-compatible SMD definitions. The target function call sets the target of the function calls (the service url) to the service action defined below.

The service action simply processes the RPC request by calling the specified function with the sent parameters.

There is a fairly large security hole opened by this implementation. Since any value can be passed to the ‘class’ argument, it is possible to remotely call functions for any class that can be auto-loaded. A ‘safer’ implementation would simply concatenate the class parameter with “Nickel_Rpc_”. This way is less likely that an attacker could compromise your server by calling a dangerous function.

Writing a RPC Class

Now that we have the RPC server set up we need some classes in Nickel_RPC to call. For the purpose of this example we will use a very simple Book class that has basic functionality:

#/library/Nickel/Model/Book.php
<?php
class Nickel_RPC_Book {
    public static function simpleEcho(){
        return “Hello World”;
    }
    public static function getTitle($bookId){
        $book = new Nickel_Model_Book($bookId);
        return $book->getTitle();
    }
    public static function setTitle($bookId, $title){
        $book = new Nickel_Model_Book($bookId);
        $book->setTitle($title);
        $book->update();
        return true;
    }
}

In the above RPC class, in addition to the simpleEcho function, we have two static functions: one that reads the title from the Book Model, and the second which writes a new title back to the database via the Book model. Notice that the functions are static: this is because, in the case of our book anyways, a notion of $this doesn’t really make since, as we don’t actually know what book we want the title for unless we pass the book ID from dojo.

Writing the RPC Front-end

Now that we have the backend written, it is fairly quick to write the frontend.

The first step is to create the book rpc file so we can include nickel.rpc.book using a dojo.require statement:

// /html/scripts/nickel/RPC/book.js
dojo.provide(‘nickel.rpc.book’);
dojo.require(‘dojo.rpc.JsonService’);

nickel.rpc.book = new dojo.rpc.JsonService(‘/rpc/smd/class/Nickel_RPC_Book’);

The above code snippet should be saved as book.js and placed in /html/scripts/nickel/rpc. When this file is included, a new JsonService object is created with functions populated from the SMD generated by the RPC controller class. As a result, nickel.rpc.book will now contain three functions: simpleEcho, getTitle and setTitle.

Using The Front-end.

Finally, we have the opportunity to see the fruit of our labour. Create an indexController and the accompanying view. In the view, after including dojo.js, create a new script block:

// /application/default/views/index/index.phtml
...
<script type=”text/javascript”>
    dojo.registerModulePath(‘nickel’, ‘/scripts/nickel’);
    dojo.require(‘nickel.rpc.book’);

    dojo.addOnLoad(function(){
        nickel.rpc.book.simpleEcho().addCallback(function(msg){
            alert(msg);      //Alert: Hello World
    });

    nickel.rpc.book.getTitle(1).addCallback(function(title){
        alert(title);    //Alert: title of book with ID 1 in database
    });

    nickel.rpc.book.setTitle(1, ‘Foo’).addCallback(function(result){
        alert(‘success’);    //Sets the title of book with ID 1 to ‘Foo’
    });
});
</script>
...

dojo.registerModulePath adds a module path to the dojo loader. This allows us to include our custom nickel modules easily and on-demand.

Conclusion

The Dojo Toolkit and Zend Framework provide a highly structured, maintainable JSON RPC system when used together. This can be used to easily propagate dirty javascript data objects to persistent storage in the backend, as well as expose PHP function calls to javascript to return useful data.

I hope you have enjoyed this tutorial. If you find any typos, mistakes or just wish to leave a message, please add a comment below.

I also encourage you to link to this post if you have found it at all useful in developing your web-application.