So the company I work for is in the process of rolling out a project for Service Oriented Architecture to provide some kind of interface between all our disparate applications; currently sitting as a mix of Java, ASP.NET, classic ASP and Drupal. Wow. I’m currently working with our Drupal based sites and oh no… I have to integrate with our “mega transformation and messaging engine”, also known as an Enterprise Service Bus using web services.
SOAP, REST or XML-RPC?
This was very much a contract first, code after project, so SOAP was needed. Yes, that meant lots of WSDL docs and XSDs… the syntax is frightening in places but it’s great in it’s own way. The stuff I’ll be discussing covers SOAP 1.1 using document/literal encoding.
You could indeed use REST… and if you’re working with XML-RPC, I would imagine you’re either supporting a legacy system or you’re really into punishing yourself!
Drupal and web services?
Two factors spring to mind with Drupal…. loose typing and the unique manner of it’s URL mapping.
Loose typing is an inconvenience. When a SOAP contract is defined it defines an element and it’s data type. So how does your underlying PHP code understand that should be treated as a boolean? The short answer is…. it doesn’t. Bummer.
What tends to happen is a lot of assumption takes place. The SOAP contract will not provide anything other than a boolean value, so you can, usually, cast your data into the required data type and help the process along. There are other issues with complex types but I will come to those later. If you’re accustomed, like I am, to the excellent capabilities of .NET to generate a class based on a WSDL you’ll find yourself stressing a bit with PHP. No matter… onwards.
URL mapping, a bit like the controller in an MVC framework, binds a given path to a module and a function, or page callback. The problem is how do you get Drupal to work with SOAP requests/posts to a given URL and if it’s possible, how does it perform?
I looked at the services module for Drupal and found it to be incomplete, slow and difficult to tailor for my project’s needs. That said, I think the concept of their module is excellent and would like to see it develop into a more polished product. In the end, something of a compromise was realised; PHP does SOAP servers and clients, Drupal is PHP – so why not create something that takes advantage of the two and avoids many of the complications?
SOAP server or SOAP client
This depends on the nature of your application. For my task I had to create both – most data going in across a SOAP server and some data being pushed out via a SOAP client. The SOAP server is more interesting, so let’s look at that…
I am aware of the NuSOAP libraries for PHP4, but as I work with PHP5 we’re lucky enough to have the native SoapServer and SoapClient objects at our disposal. The SoapServer client is easily set up with a few lines of code:
< ?php
// see http://www.php.net/manual/en/soapserver.soapserver.php for a comprehensive examination
$server = new SoapServer("some.wsdl");
$server->addFunction("YourFunctionName");
$server->handle();
?>
That’s it. The hard part comes with writing your function and getting your WSDL generated, or written, correctly.
Interpreting your request
Ok, let’s assume we have a WSDL doc which defines a quick function to add data about a user. It defines the following elements – I’ll use pseudo-code for this as I haven’t got a WSDL generator at hand:
* email (string)
* active (boolean)
* roles (array of strings)
/**
* function accept and process a SOAP request
*/
function AddData($request) {
$email = (string)$request->email;
$active = (bool)$request->active;
$roles = (array)$request->roles;
}
Note how elements within the SOAP request are always accessed as you would access object properties. You might be able to use array syntax, but I’m not sure if this would work reliably… if it all.
Most common gotchas when processing SOAP requests
- Is it a string, or an array!? – the answer is… it depends. Say $request->roles only contains a single element – PHP doesn’t know the type, only sees a single element and assumes it’s a string. If there is more than a single element, PHP assumes it’s an array. This has caused me some serious ball aches when I’m expecting an array with a single element.
- Booleans… yes, 1, or true? – sadly another area of ambiguity. I would argue, for consistency, people use the string representation (ie: true/false). However, that’s not always guaranteed and PHP evaluates “Yes” and “No” to 1 and 1 respectively. Not sure why, but quickly solved with a function to force “Yes”/”No” into the right form.
Integrating with Drupal
You could do what I did at first by writing some independent PHP which happens to interact with the same database as your Drupal site. That’s not so great as it doesn’t allow you to use many of good things Drupal has – such as the database abstraction library, variable system or access to your module code (code re-use!!).
A chap I work with (http://codeofrob.com) suggested bootstrapping Drupal to a minimal level to achieve this. So my SoapServer started to look like this:
< ?php
require_once './includes/bootstrap.inc';
/**
* Sixth bootstrap phase: load bootstrap.inc and module.inc, start
* the variable system and try to serve a page from the cache.
*/
drupal_bootstrap(DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE);
require_once './includes/common.inc';
$serviceName = $_GET['s'];
include_once './services/' . $serviceName . '/service.php';
Let's look at what's going on here. This file lives in the root of my site and acts as an interface to all my disparate services. More importantly, this file is bootstrapping Drupal up to the DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE level which allows me to take advantage of many aspects of the system. Ultimately - this is a process which is comprised of several server side includes (for the various modules) and a few global variable loads. It stops short of fully bootstrapping Drupal so there is a performance bonus. Of course, if I needed access to something which requires a full bootstrap I would have to increase this.
Integration with Drupal as a fully fledged module
I've not looked into this fully, but I believe this would be the nicest, most usable format for a web services module to reside in. There are a number of concerns I have about this:
- Speed. The services module is not good (at the time of writing this) in terms of performance and functionality.
- Authorisation. Can you use anything other than forms based authentication? I currently use HTTP basic authentication or IP based access - I'm not sure this would be particularly easy to do directly in Drupal.
- Focus. Web services are massively varied - both in terms of messaging formats, protocols and payloads. Could a module with a fancy admin interface abstract some of this away? Difficult job if you ask me!
Wrapping up
I have to admit I've only glossed over this topic for now as I'm still understanding some parts of it. My intention is to provide an alternative look at integrating your Drupal site with some of the more monolithic pieces of an enterprise scale system.
Another great resource is this book: Pro PHP XML & Web Services (Books for Professionals by Professionals) - it was a real help in understanding SOAP, web services and more importantly - how I could get PHP to work with it all.