Zend Framework 2 – JSON-RPC Server

Last modified date

Comments: 0

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.

[code lang=”php”]
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’)
);
}
}
[/code]

And then our server would look something like this:

[code lang=”php”]
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();
}
[/code]

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:

[code lang=”js”]
{
"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"
}
}
}
[/code]

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:

[code lang=”js”]
{
"method": "ping",
"id": null
}
[/code]

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

The response we’d get from that would be:

[code lang=”js”]
{
"result": 1345757761,
"id": "",
"jsonrpc": "2.0"
}
[/code]

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

[code lang=”js”]
{
"method" : "ab",
"id": null,
"params":
{
"a": 10,
"b": "test"
}
}
[/code]

And the response would be:

[code lang=”js”]
{
"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"
}
[/code]

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.

Share

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.