A Complete PHP REST solution

by John McGeechan

Synopsis

A client requested the construction of a limited set of web services. Simpler solutions created the foundations for these services via a set of generic classes and libraries.

Details on how to implement these are given below

Download

Download the files from here and unzip onto the docroot .

Requirements

Pear packages

HTTP/Request.php

Net/Socket.php

Net/URL.php

PEAR.php

simpletest

download suite (http://www.simpletest.org) ensure it is installed in a directory 'simpletest' on the include path

Installing

Download the zip file and unzip onto the docroot . Change the following fields

$this->__set('C_WEBHOME_SERVICES','http://yourbaseurl');

and your SQL connection details...

$this->__set('sqlType','mysql');

$this->__set('sqlHost','localhost');

$this->__set('sqlUser','username');

$this->__set('sqlPass','password');

$this->__set('sqlDb','database');

 

Accessing REST services or providing them ?

Most of the logic contained in these files aims to provide REST functionality to your site, to allow other parties to access your data through RESTful calls. However, there are also example usages for accessing REST web services directly from your own server (server-to-server) or via RESTful calls made through ajax/javascript calls.

 

Setting up the tests

Before proceeding further it may be worthwhile checking that the tests work on your site. The installation files include a set of test RESTful classes that will provide responses when RESTful calls are made to them. The classes can be found in ~/library/services. The tests make simple RESTful calls to these classes via HTTP. This may not be possible though if for instance your site has a firewall that prevents outgoing HTTP requests. Assuming that this is not the case then perform the following steps in order to run the tests.

  •  create a new 'testtable' on your site. This table will be used by one of the test services. SQL creation script can be found in ~/sql/testTableCreateScript.sql
  • point your browser to http://yourbaseurl/tests/testserviceFunctions.php

If the tests have worked then you should see the success green bar at the bottom of the screen

RESTful calls to your server

Once the files are copied to the docroot (as detailed above) and the tests have been run. RESTful services should be available on your site. To confirm this, open a browser window and enter the following url...

http://yourbaseurl/index.php?dataFormat=xml&service=exampleService&height=1.0&knowssnowwhite=y

This should return a set of data in xml format, the example service returns a list of dwarfs over a certain height that know snow white. All web services are invoked in exactly the same fashion eg.

http://yourbaseurl/index.php?service=serviceName&dataFormat=xxx,parm1=aaa,parm2=bbb

 

service - mandatory field, the name of the service, it could be simply a top level service eg "foo" or it could be a sublevel service eg "foo/bar", in this way web services can be organized within directories

dataFormat - mandatory field, must be of the form 'xml','json','jsonp'

parm - a name/value pair(s) to pass to a specific service. Note each service will validate that it has received the correct parameters to operate

 

Creating a new web service

All web services are held in directory ~/library/services. Each web service must follow the "convention over configuration" naming convention where the file name and the class name are the same ie the service exampleService.php holds a class exampleService, similarly service fooBar.php would contain a class fooBar and would be invoked as

http://yourbaseurl/index.php?service=foobar&dataFormat=xxx,parm1=aaa,parm2=bbb.

Each web service must extend the baseService class and implement the servicesInterface interface, look in any of the example services to see how this is done. Each web service must contain at least these 3 methods ...

processWebRequest - this grabs the data in whatever way is convenient to you, but must return it as an array

defineFields - this returns an array of the parameters that are expected by this service.

getServiceDescription - returns a string that explains what the service does

 

NB these 3 methods are enforced by the interface

The centrally invoked method of the web service will always be "processWebRequest", of course you are free to build your web services in any way you see fit, the only caveat being that the processWebRequest method must return an array (or null).

The "defineFields" method serves two purposes, it ensures that generic checks can be made in the base class to ensure that the expected fields were set and also provides a handy "signature" for documentation (see later). Note that the array returned by the "defineFields" method contains the following attributes...

 

fieldName - (string) the expected field name

fieldDescription - (string) a description of the field

mandatory - (boolean) should this process abort if this field has not been specified

cast - (string) should this field be cast to a certain format , must be one of  'int','float','bool','string' (cast to string by default)

 

Note that the processWebRequest method can return a query result set in array form or just a hard coded array, if this is more appropriate. The only caveat is that the results are in an associative array format (a normal array would be nonsensical, but is not prohibited).  If one looks at the processWebRequest method for the exampleDataStructure service, this simply returns an array of data...

return array (

"football team" => "Liverpool",

"Transport" => array("car" => "mini", "bike" => "dawes Watoga"),

"Favourite Food" => array("fruit" => array("apples" => "bramleys","orange" => "jaffa","pear" => "conference" ),

"meat" => "beef",

"junk food" => array("burgers" => "Greasy Ron's", "chicken" => "the colonel","pizz" => "pizza hut"))

 );

Returning data from the database

If the service needs to return rows from the database, some basic scaffolding is in place to accomplish this. The exampleService web service returns rows from a table 'testtable' depending on certain parameters. SQL is abstracted out into the ~/sql library rather than being held in the service class itself, so SQL calls are achieved using a base class handler method "processQuery" which takes the following parameters...

 

sqlLibrary - a class file held in the library ~/sql.

sqlMethod - a class method defined in the library

parms - an array of parameters

 

. Again in true "convention-over-configuration" style, the sqlLibrary paramater represts the file name and it's corresponding class name. The sqlMethod represents the method within that class. And parms is simply an array of  values (in order) that are bound to the placeholders within the query (refer to the exampleService web service to see how this is done).

It is of course fine for you to access the database in any way that seems suitable to you rather than use this method, but please don't use strings entered by the user directly into your queries ! You may as well simply say " please attack my site with SQL-injections !". All user-entered data must be santitised using bind statements and placeholders or the like (or even stored procedures if you prefer !).

Note that one advantage of using the processQuery method is that it will always return an associative array of field names and field values. That is to say that it is not necessary to reformat the returned SQL into a digestible associative array for handing back to the requestor.

 

AJAX and your web services

The web services can be used seamlessly with any ajax employed on your site. A simple request to the web service for data in a 'json' format will be all your javascript needs to extract server-side data. An example has been provided in ~/client/jsonClient.html. This example uses jquery to make a call to the exampleService installed on your site. Note also within jsonClient.html there is an example of calling a site other than your own ! A call to the yahoo api to get information regarding the author "Kahlil Gibran". This would not work if the data were requested in xml or json format due to the "same origin" security policy inherent in browsers, this does work however because the data is requested in the "jsonp" format, which is handled seamlessly by jquery (an explanation of the jsonp content type is beyond the scope of this document but more information can be found here).

 

What about making server to server calls ?

On occasion you may wish to make RESTful calls from your server to another. This may be to access some data to complete a background task or because the server does not allow jsonp calls and so your server needs to act as an ajax proxy. Whatever the reason, a helper class has been provided at ~/client/client.php (Nb to see an example of this operating run the ~/client/clientUsage.php script, this invokes a server-side RESTful call and echos the result)

 

3rd parties and your web services

For ease a complete example has been provided for distribution to any client that wants to access your web services. This can be found in ~/client/3rdparty/exampleClient.html and is virtually identical to the jsonClient.html outlined earlier.

NB ensure that all files in directory ~/client/3rdparty are distributed and that file exampleClient.html is amended to reflect the location of these files on the client server

 

Documenting your web services

A facility has been provided to document all of your available web services. This can be used in your own site-wide documentation as an aide-memoir or as documentation for partners. Simply point a browser to ~/showServices.php , to list all of the web services currently offered (~/showServicesSmall.php simply shows the same thing minus any head or body tags so that this can be included in another file via an ssi or the like)

nb if their are services that should be omitted from the list, add it to the array "exemptFiles" in ~/functions/listServices.php

Future Development

future versions of this framework may include ...

config - replace current config processing with PEAR config class

service throttling - allow services to be toggled on or off. So that at peak traffic times or due to maintenance, services can be taken off-line

caching - at either service or service/argument level granularity

token - some services to be restricted to the use of tokens ie a request is refused without an allowable token

encryptedToken - for security, to ensure that sensitive data can only be accessed by appropriate parties

cookies - generic cookie processing ie you have to be logged in to see the data

generic paging - allow parameters in to handle paging through data (this can obviously be handled anyway by individual requests, but it may be better to have this in the services base class to prevent duplication

parameters in database - it may be better to abstract arguments/service description etc into the database

 

Trouble shooting

Web services can be a pain. The results can be unpredictable and provide very little feedback for debugging (especially true of jsonp calls). One good rule of thumb is to double check that calls are being made correctly. Use LIVE HTTP HEADERS on mozilla or the equivalent on IE to ensure that calls are syntactically correct, are to known services and are indeed returning data. Rememeber if

http://yourbaseurl/index.php?service=foobar&dataFormat=xxx

doesn't return anything in a browser, it won't return it in your AJAX or PHP server-to-server call either !

 

Last resort

As a last resort, try contacting us and if we have time we will try to answer your query.

Download Link does not work.

Download Link does not work.

This is great

Hey thanks for this. Saved me a whole lot of time !

Post new comment

By submitting this form, you accept the Mollom privacy policy.