Tutorial Part 1: REST with Dojo and PHP
I only started recently to dig into REST, so I’m in no way an expert in this field. There are not that many tutorials on the internet, and the ones I found were only of limited help. They all seemed to miss something or, rather, I missed something. So I decided to write my own tutorial about my findings to help others that might have the same problems. On the client side, I’ll use the Dojo Toolkit, on the server side, PHP.
I will focus mainly on providing and explaining some working code examples including server PHP code. At the end of this article you can find all code as a downloadable zip-file. As an introduction to REST, read some of the tutorials linked at the end of this article, because I’m not going to repeat some of the things you can read there.
- Jump directly to the demo of part 1: Check availability of HTTP request methods
- Jump directly to the demo of part 2: Lazy load, update, create and delete dijit tree items
1. Url-confusion
What confused me the most at the beginning was the structure of the REST urls, for example in the article Create a REST API with PHP. How on earth can an url be structured like:
PUT request to /api/users/1 – Update user with ID of 1
Where’s the PHP page in this url that receives this request? And second, I always thought variables had to be posted with a querystring. It took me a while to figure out that the path in the url does not necessarily have to end with a filename such as mypage.php and that in this example it is simply omitted. Furthermore, /api/users/1 is not a sort of variable, but just part of the path to the resource. To make this clear, just add a PHP filename in front of the resource…
mypage.php/api/users/1
… and then you can process the request on the page mypage.php with:
$resource = $_SERVER['PATH_INFO']; $method = $_SERVER['REQUEST_METHOD'];
Where the variable $resource would contain:
/api/users/1
and $method would contain:
PUT
To extract additional variable data from the request body or request header you can use parse_string() and file_get_contents() or $_GET.
if ($method == 'POST' || $method == 'PUT') {
parse_str(file_get_contents('php://input'), $_DATA);
}
else {
$_DATA = $_GET;
}
The above code is exactly what I use in the demo file php-rest-check.php:
<?php
$resource = $_SERVER['PATH_INFO'];
$method = $_SERVER['REQUEST_METHOD'];
if ($method == 'POST' || $method == 'PUT') {
parse_str(file_get_contents('php://input'), $_DATA);
}
else {
$_DATA = $_GET;
}
$arr = array(
'method' => $method,
'resource' => $resource,
'data' => $_DATA
);
// send (fake) HTTP status responses if forceError is set
switch($method) {
case 'POST':
if ($_DATA['forceError'] == 'false') {
$status = 'HTTP/1.1 201 Created';
}
else {
$status = 'HTTP/1.1 500 Internal Server Error';
}
break;
case 'GET':
if ($_DATA['forceError'] == 'false') {
$status = 'HTTP/1.1 200 OK';
}
else {
$status = 'HTTP/1.1 500 Internal Server Error';
}
break;
case 'PUT':
if ($_DATA['forceError'] == 'false') {
$status = 'HTTP/1.1 200 OK';
}
else {
$status = 'HTTP/1.1 404 Not Found';
}
break;
case 'DELETE':
if ($_DATA['forceError'] == 'false') {
$status = 'HTTP/1.1 200 OK';
// With 204 there would be no status code available on the client-side
//$status = 'HTTP/1.1 204 No Content';
}
else {
$status = 'HTTP/1.1 500 Internal Server Error';
}
break;
}
header($status);
if ($_DATA['forceError'] == 'false') {
// only send response data back when successful
echo json_encode($arr);
}
?>
2. Sending the RESTful requests
The only remaining question now is how you send a PUT or DELETE request. For that I’ll use the Dojo Toolkit, because that makes it very easy and I’m a big fan of it. If you don’t know Dojo, sorry, but it’s worth learning. The demo is meant as a working example to check if your server provides all four methods. The toolkit provides for each its own method, e.g. dojo.xhrPost(), dojo.xhrGet(), dojo.xhrPut() and dojo.xhrDelete().
First we define a general argument object for all requests, because some attributes are all the same. If you want to send some variables along with your request, just add them as the content property. Dojo will either send them as part of the request header (e.g. in the querystring) in case of a GET/DELETE or as part of the request body in case of a PUT/POST. Use firebug to check what I mean. I added a forceError variable to tell PHP to send fake HTTP error messages.
<script type="text/javascript">
var demo = {
// general arguments object for the different xhr methods.
args: {
url: 'php-rest-check.php',
handleAs: 'json',
content: {
forceError: false, // if set to true, server responds with an HTTP error
var1: 'someVal1', // just some variables
var2: 'someVal2'
},
load: function(response, ioArgs) {
// log response
dojo.byId('log').innerHTML+= '<p>HTTP status: ' + ioArgs.xhr.statusText + ' ' + ioArgs.xhr.status + '<br/>' +
'method: <strong>' + response.method + '</strong><br/>' +
'resource: ' + response.resource + '<br/>' +
'query: ' + dojo.objectToQuery(response.data) + '</p>';
},
error: function(error) {
// log HTTP error status
dojo.byId('log').innerHTML+= '<p>HTTP error status: ' + error.status;
}
},
...
Then, all that’s left is that you need to slightly adapt the args object for each request. Note the dojo.clone() method. I use it to work with a copy not a reference of the args object.
...
// create
post: function() {
var args = dojo.clone(this.args); // otherwise we would keep adding '/name/1' with each new post
args.url+= '/images/1'; // create new item with id = 1 at '/images'
dojo.xhrPost(args);
},
// read/load
get: function() {
var args = dojo.clone(this.args);
args.url+= '/images'; // load all items at '/images'
dojo.xhrGet(args);
},
// update
put: function() {
var args = dojo.clone(this.args);
args.url+= '/images/1'; // update item with id = 1 at '/images
dojo.xhrPut(args);
},
// delete
del: function() {
var args = dojo.clone(this.args);
args.url+= '/images/1'; // delete item with id = 1 at resouce
dojo.xhrDelete(args);
}
}
...
Finally, add the calls to the links on page load:
...
dojo.addOnLoad(function() {
// add each request method to one of the links
dojo.query('#ulDemo li').forEach(function(node, i) {
dojo.connect(node, 'onclick', function(e) {
dojo.stopEvent(e); // prevent link action
demo.args.content.forceError = dojo.byId('fldforceError').checked ? true : false;
switch(i) {
case 0:
demo.post();
break;
case 1:
demo.get();
break;
case 2:
demo.put();
break;
case 3:
demo.del(); // note: delete is reserved word
break;
}
});
});
});
</script>
Tutorials
- Gen X Design: Create a REST API with PHP
- Medryx Observations: JsonRestStore — Custom Services, Schemas, and Lazy Loading
- Sitepen: Efficient Lazy Loading of a Tree
- Sitepen: Effective use of JsonRestStore: Referencing, Lazy Loading, and more
Downloads
zipped demo file of part 1 php-rest-check.zip
zipped demo file of part 2 dojo-jsonreststore.zip
That’s the end of the first part. Second part in preparation…
