Well, nuts to that.
What web services really amount to is nothing more than remote procedure calls that you can make from a web browser. That's cool enough. You make a request, and get a response. Instead of sending HTML that you render, the web service just returns the data. Most web services today use XML to package that data, though a few places -- like Yahoo -- are starting to use JSON instead.
JSON has a few major advantages over XML. First, it is smaller: less bandwidth is wasted by
For an upcoming project, I wanted to develop a web service. There will be PHP interacting with the database, and I want to make simple calls from the browser. The frontend will be a Javascript application, and it is not page-based at all. So I needed a way to pass data back and forth between the browser and the server simply and quickly.
I investigated using SOAP, but was underwhelmed. I decided to design my own web service in PHP, which would use JSON for all its data passing.
The first thing I needed was to be able to register functions with the service, such that only functions that are actively made public are available. I quickly put together a PHP class with a "methods" instance variable and a "register" instance method.
Each function will take a single parameter, but this parameter is expected to be an arbitrary JSON object, which can hold any amount of data. So rather than calling a function like sum(1,2), you would call the function sum($par), where $par = {one:1, two:2}. Then you parse that parameter object in the sum() function and handle it appropriately. It's an extra step and a bit more to remember, but I feel that it adds plenty of flexibility. For example, I wrote a sum function using this brand of parameter passing, and it was utterly trivial to make it work for an arbitrary number of values.
function sum($par) {
$sum = 0;
foreach ($par as $key=>$val) {
$sum += $val;
}
return array("sum"=>$sum);
}
$sum = 0;
foreach ($par as $key=>$val) {
$sum += $val;
}
return array("sum"=>$sum);
}
Oh yes. I forgot to mention how the return values work. Rather than simply returning a single value, I will always be returning an associative array/object (the PHP associative array type is most closely related to the Javascript object type). This way I know what the return value is (it's called the "sum"), and for more complicated functions I can return as much data as I want.
In order to serve a request, which should be a POST with two parameters ("method" and "param"), I need to decode the param parameter from JSON into a PHP-native object, check that the method is registered, and then call the method with the parameter. I then take the function's return value, JSON-encode it, and return it to the browser.
Here is the code for the web service (sswebservice.php):
<?php
require_once("JSON.php");
class SSWebService {
var $methods;
var $json;
function initialize() {
$this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
}
/*
add a function to the server, so it can be accessed
*/
function register($name) {
$this->methods[$name] = true;
}
/*
set a registered function to be inaccessible
*/
function deregister($name) {
$this->methods[$name] = false;
}
/*
execute the given method, passing its single parameter
JSON-encodes the return value, which should be an object or associative array
*/
function call($name, $param) {
if ($this->methods[$name] == true) {
$evalstring = $name."(\$param);";
eval("\$rval=".$evalstring.";");
return $this->json->encode($rval);
}
}
/*
decode the JSON param into a native object, and call the given method
return the JSON-encoded object to the browser via echo
*/
function serve($method, $param) {
$obj = $this->json->decode(stripslashes($param));
if ($this->methods[$method] == true) {
$res = $this->call($method, $obj);
} else {
$res = $this->json->encode("Not a registered function.");
}
echo $res;
}
}
?>
require_once("JSON.php");
class SSWebService {
var $methods;
var $json;
function initialize() {
$this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
}
/*
add a function to the server, so it can be accessed
*/
function register($name) {
$this->methods[$name] = true;
}
/*
set a registered function to be inaccessible
*/
function deregister($name) {
$this->methods[$name] = false;
}
/*
execute the given method, passing its single parameter
JSON-encodes the return value, which should be an object or associative array
*/
function call($name, $param) {
if ($this->methods[$name] == true) {
$evalstring = $name."(\$param);";
eval("\$rval=".$evalstring.";");
return $this->json->encode($rval);
}
}
/*
decode the JSON param into a native object, and call the given method
return the JSON-encoded object to the browser via echo
*/
function serve($method, $param) {
$obj = $this->json->decode(stripslashes($param));
if ($this->methods[$method] == true) {
$res = $this->call($method, $obj);
} else {
$res = $this->json->encode("Not a registered function.");
}
echo $res;
}
}
?>
And here is a demonstration web service that uses it (jsontest.php):
<?php
require_once('sswebservice.php');
$server = new SSWebService;
$server->initialize();
function helloWorld($par) {
return array("string"=>"Hello, world!");
}
function hello($par) {
return array("string"=>"Hello, ".$par["name"]."!");
}
function sum($par) {
$sum = 0;
foreach($par as $key=>$val) {
$sum += $val;
}
return array("sum"=>$sum);
}
$server->register("sum");
$server->register("helloWorld");
$server->register("hello");
$method = $_POST["method"];
$param = $_POST["param"];
$server->serve($method, $param);
?>
require_once('sswebservice.php');
$server = new SSWebService;
$server->initialize();
function helloWorld($par) {
return array("string"=>"Hello, world!");
}
function hello($par) {
return array("string"=>"Hello, ".$par["name"]."!");
}
function sum($par) {
$sum = 0;
foreach($par as $key=>$val) {
$sum += $val;
}
return array("sum"=>$sum);
}
$server->register("sum");
$server->register("helloWorld");
$server->register("hello");
$method = $_POST["method"];
$param = $_POST["param"];
$server->serve($method, $param);
?>
As you can see, this web service has three available functions: sum, hello, and helloWorld. Now I just need a way to call them from the browser.
I tend to use the Prototype/Scriptaculous libraries for my web development, so I first whipped something up using Prototype's Ajax.Request object, but that turns out to be quite a bit of code every time I want to make a call to my web service. So I created a very simple class with nothing in it but a class method (ssclient.js):
function SSClient() {
}
SSClient.call = function(url, method, obj, callback) {
var param = "method=" + method + "¶m=" + obj.toJSONString();
new Ajax.Request(url, {
parameters: param,
onSuccess: function(req) {
var rval = req.responseText.parseJSON();
callback(rval);
},
onFailure: function(req) {
alert("Call failed.");
}
});
}
}
SSClient.call = function(url, method, obj, callback) {
var param = "method=" + method + "¶m=" + obj.toJSONString();
new Ajax.Request(url, {
parameters: param,
onSuccess: function(req) {
var rval = req.responseText.parseJSON();
callback(rval);
},
onFailure: function(req) {
alert("Call failed.");
}
});
}
So when I want to make a call to my web service, I simply need to supply the URL, the method I want to call, the Javascript object I want to send, and a callback function to execute when it returns. The callback function should expect to receive a JS-native object, since I parse the JSON-encoded object before calling the callback function.
Here is an example of the code to call the web service:
function button1() {
SSClient.call("jsontest.php", "sum", {
one: 1,
two: 6,
three: 9
}, sum_callback);
}
function sum_callback(r) {
alert(r.sum);
}
SSClient.call("jsontest.php", "sum", {
one: 1,
two: 6,
three: 9
}, sum_callback);
}
function sum_callback(r) {
alert(r.sum);
}
As you can see, the SSClient.call() function is very easy to use, and I can create an anonymous object to pass along. In this case, the callback function simply pops up the return value. Note that it is accessed by r.sum, which is because of how I set up my return values in jsontest.php. Neat.
This requires Prototype, json.js from http://www.json.org/json.js, and JSON.php from http://mike.teczno.com/JSON/JSON.phps.
And now I'm off to build an application with my new web service!