Recently, with our company’s update to PHP5 (yeah yeah, I know) we were looking to replace the software we use on our web service to handle real-time payments and customer lookups. Since we’ve been running PHP4, our natural choice was the NuSOAP framework. It gave us a (relatively) simple way to create the service and was easy to use. Unfortunately, with the move to PHP5, we couldn’t really use it anymore – and yes, I’ve seen the “change this line to this” fix for it but that kind of stuff makes me nervous. So, I decided that using PHP5’s SOAP extension was the way to go. Thankfully, it’s been pretty smooth sailing so far, and I thought I’d share some of it to maybe help anyone else out there looking to do something similar.
First off, my goal for creating this new service was to make it as flexible as possible. I wanted to make the main server portion of it smart enough to call what was needed to make the request without having to load the functionality for everything into each server request. The general idea, then, is to have the main server assigned to the SOAP object that dynamically loads in a class for that type of request. Thankfully, PHP5 has some features that make this pretty simple.
Here’s some sample code for the server portion:
[php]
header(“Content-Type: text/xml”);
function __autoload($class){
$path=’/path/to/webservice/libs/’.$class;
if(is_file($path)){ include_once($path); }
}
class MyServer {
var $_valid = null;
var $_action = null;
var $_msg = null;
var $_pars = array();
function __construct($msg){
$this->_msg=$msg;
}
function __parse($msg,$args){
$dom = DOMDocument::loadXML($msg);
$xpath = new DOMXPath($dom);
$result = $xpath->query(“//SOAP-ENV:Envelope/SOAP-ENV:Body/*”);
foreach($result->item(0)->childNodes as $key => $value){
$this->_action = $value->parentNode->tagName;
$this->_pars[$value->tagName] = $args[$key];
}
unset($dom,$xpath,$result);
}
function __call($func,$args){
$obj = new $func($args,$this->_msg);
if(isset($obj->xsd_type)){
switch($obj->xsd_type){
case ‘array’: return new SoapVar($obj->data,SOAP_ENC_OBJECT,$obj->type); break;
case ‘string’: return new SoapVar($obj->data,XSD_STRING,$obj->type); break;
case ‘xml’: return new SoapVar($obj->data,XSD_ANYXML,$obj->type); break;
default:
return new SoapVar($obj->data,SOAP_ENC_OBJECT,$obj->type);
}
}else{ return new SoapVar($obj->data,SOAP_ENC_OBJECT,$obj->type); }
}
}
//Now we start up our server…
$input = file_get_contents(“php://input”);
$server = new SoapServer(‘http://www.example.com/my.wsdl’);
$server->setClass(“MyServer”,$input);
$server->handle($input);
[/php]
It’s not the simplest script, but I wanted to show you all of the parts first and then break it down into more manageable chunks. Let’s start where the flow starts – the bottom of the script. This chunk of code allows the script to intercept the incoming request and assigns the class to handle it. Our MyServer class is where the real fun starts.
Moving along the flow line, we hit the constructor of the class. The only thing we do here is assign the incoming message to the _msg property of the class. This is so that later, when we need to parse it, we can get at it. Some of the magic with the SOAP PHP functionality happens next – it tries to look for a function named the same as the request type (so if the SOAPAction is “getFoo” it looks for the “function getFoo()” in the class). I went a different route here, though – I didn’t define any of the methods it would need in the server class itself. Instead, I made use of the __call magic function to handle things.
When the __call catches the action, it gets the function name that was called and the arguments it was called with. Unfortunately, this argument array has numeric keys which made it hard for me to tell what the real order of the inputs was (they could have the “acct_num” in front of “last_name” in one request and after it in another). More on that later, though. Right inside the __call method, you’ll see the key to it all – the dynamic call to an object, based on the type of request, being created and called with the arguments and a copy of the message. Since the classes aren’t included yet – we only wanted what we needed – the __autoload kicks in and pulls in the file.
This brings us to one of our “action classes” – here’s an example:
[php]
class MyTestRequest extends MyServer {
function __construct($args=null,$msg){
parent::__parse($msg,$args);
$arr=array(‘testing’=>’1,2,3′);
$this->type=’Account’;
$this->data=$arr;
}
}
[/php]
This is the simplest kind of request we can have – it lives off in the directory (defined in the __autoload of the server) as a file called MyTestRequest.php. All of the action happens in the constructor and other functionality should probably be relegated to other methods on the class. Our server class calls this constructor with the two parameters which are then passed back to a function in the server class, __parse. This does something that’s a bit optional, but I wanted it to be sure I knew which parameter was which when they were passing it it. As it stood, I only had numeric indexes on the arguments array and was left to guess which order they were in (not good). To remedy the situation, I pass it off to this function for processing.
The __parse function loads the message into a DOM document and, using an XPath query, finds the node for the action inside the SOAP:BODY. Each of its child nodes are then pulled out and the tag names are assigned to the _pars array and the action to the _action property. With these set, we can come back and, instead of just assuming that $args[0] is the account number, we can know for a fact that $this->_pars[‘acct_num’] is the right information.
The $arr inside of the __construct in MyTestRequest is then passed back out to the script via the $this->data property (shared between the parent and child since it extends it) and the type for the response (from the WSDL) in $this->type.
Hopping back over to the MyServer class, we’re back inside the __call still and there’s just one last thing to do – echo out the response in a SOAP-friendly way. That’s what all of the SoapVar calls are for. The switch() call looks at an optional $this->type property and, of set, tries to match the output with the right data format. In our example, though, it just falls down to the “else” and calls it as an object.
And, voila – it’s done. The output is a formatted SOAP response!
Hi,
thanks for the nice article.
As far as I could read, PHP SOAP extension does not support attachment. That’s why I eventually turned to PEAR::Soap library.
Regards,
Karl3i.
LikeLike