Zend Framework 2 – JSON-RPC Server

Creating a JSON-RPC server in ZF2 is very similar to how you would do it in ZF1, so you could pretty much follow the documentation for version 1. However, here is an example of how to implement with version 2.

First you would start with a class to define your service. It’s important that you include the doc blocks because Zend\Json\Server will use Reflection to analyse your class and provide a service map (ie, what methods are available, what parameters they take, etc.) and the @param and @return attributes in the doc block are also used… plus it’s just good practice.

So as an example, this is a very simple class providing a couple methods. The first is to return the current time – a handy little method to have simply to validate the service is working – and the second returns a mock complex object just to give an interesting result.

namespace Amnuts;

use Zend\Json\Server\Exception\InvalidArgumentException;

class Server
{
    /**
     * Return the current timestamp
     *
     * @return int
     */
    public function ping()
    {
        return time();
    }

    /**
     * Return a complex object related somehow to $a and $b
     *
     * @param int $a
     * @param string $b
     * @return stdClass
     */
    public function ab($a, $b)
    {
        if (!is_int($a)) {
            throw new InvalidArgumentException('Param $a needs to be an integer');
        }
        if (!is_string($b)) {
            throw new InvalidArgumentException('Param $b needs to be a string');
        }
        return array(
            'a' => 10 + $a,
            'b' => 'foo: ' . $b,
            'data' => array(
                'idx' => $a.$b,
                'complex' => array(
                    'letter' => 'g',
                    'range' => range($a, $a+5)
                )
            ),
            'other' => range('e', 'j')
        );
    }
}

And then our server would look something like this:

use Zend\Loader\StandardAutoloader,
    Zend\Json\Server\Server,
    Zend\Json\Server\Smd;

require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new StandardAutoloader();
$loader->setOptions(array('autoregister_zf' => true))->register();

header('Content-Type: application/json');

$jsonrpc = new Server();
$jsonrpc->setClass(new Amnuts\Server());
$jsonrpc->getRequest()->setVersion(Server::VERSION_2);

if ('GET' == $_SERVER['REQUEST_METHOD']) {
    $smd = $jsonrpc->getServiceMap()->setEnvelope(Smd::ENV_JSONRPC_2);
    echo $smd;
} else {
    $jsonrpc->handle();
}

As you can see, we first define the classes we’re going to be using from the framework, then set up the standard autoloader and then we can go on to set up the JSON-RPC server.

With the server, I’m including my class that will be doing the heavy lifting with the setClass method and I’m ensuring that it’s going to be version 2.0 of the JSON-RPC spec with the setVersion call.

What follows after I’m instantiated and set up the server class is the handling of the requests. The GET method is intercepted so that we can provide the server map should the client perform a simple GET on the server end-point. You don’t have to provide this service map if you don’t want to, in which case you’d just have the handle method call in place of the if/else block.

Given the above code, if we were to access the end point with a GET request we’d get the following result:

{
   "transport": "POST",
   "envelope": "JSON-RPC-2.0",
   "contentType": "application/json",
   "SMDVersion": "2.0",
   "services":
   {
       "ping":
       {
           "envelope": "JSON-RPC-2.0",
           "transport": "POST",
           "parameters":
           [
           ],
           "returns": "integer"
       },
       "ab":
       {
           "envelope": "JSON-RPC-2.0",
           "transport": "POST",
           "parameters":
           [
               {
                   "type": "integer",
                   "name": "a",
                   "optional": false
               },
               {
                   "type": "string",
                   "name": "b",
                   "optional": false
               }
           ],
           "returns": "object"
       }
   },
   "methods":
   {
       "ping":
       {
           "envelope": "JSON-RPC-2.0",
           "transport": "POST",
           "parameters":
           [
           ],
           "returns": "integer"
       },
       "ab":
       {
           "envelope": "JSON-RPC-2.0",
           "transport": "POST",
           "parameters":
           [
               {
                   "type": "integer",
                   "name": "a",
                   "optional": false
               },
               {
                   "type": "string",
                   "name": "b",
                   "optional": false
               }
           ],
           "returns": "object"
       }
   }
}

Calling the methods can simply be a case of posting the correct JSON string to the server. If we wanted to call the ping method we may perform a POST request with the following:

{
    "method": "ping",
    "id": null
}

(“id” is a required parameter to be passed to the server).

The response we’d get from that would be:

{
  "result": 1345757761,
  "id": "",
  "jsonrpc": "2.0"
}

And for the ab method the POST request would look something like:

{
    "method" : "ab",
    "id": null,
    "params":
    {
        "a": 10,
        "b": "test"
    }
}

And the response would be:

{
  "result": {
    "a": 20,
    "b": "foo: test",
    "data": {
      "idx": "10test",
      "complex": {
        "letter": "g",
        "range": [
          10,
          11,
          12,
          13,
          14,
          15
        ]
      }
    },
    "other": [
      "e",
      "f",
      "g",
      "h",
      "i",
      "j"
    ]
  },
  "id": "",
  "jsonrpc": "2.0"
}

You will notice that it converts any associative arrays to objects and leaves any standard arrays as such.

As a side note, I did all my testing from the Firefox plugin-in called RESTClient (also on GitHub) – I highly recommend it for testing and debugging a REST service.

Did you like this? Share it:

Leave a Reply