SOAP to Array

The Scenario

Many (legacy?) PHP implementations of reading a soap response use simplexml_load_string() combined with xpath() to extract a value from the SOAP response, something like this.

<?php
libxml_use_internal_errors(true);
$xml = simplexml_load_string($response);

if (!is_object($xml)) {
    libxml_clear_errors();
    throw new Exception('Failed to load XML from service');
}

 $status = $xml->xpath('PATH/TO/STATUS');
 $real_status = $status[0];
 $callback = $xml->xpath('PATH/TO/CALLBACK');

 // Do something with values

The problem this creates is if the response changes, the object requires modification, violating the "O" in SOLID. If additional values from the response are required, in we go again modifying the code. Multiply this by every place a SOAP response is parsed, you have a bunch of code duplication scattered around the code base and none of them are unit testable. The SoapToArray object was created to provide a single point in which to parse any SOAP response reliably and easily into a standard array. A second object, SoapNodeUtilities, was created to easily access the required parts of the array without deeply nested array references such as $var[‘heavily’][‘nested’][‘array’][‘member’][‘somewhere’][‘down’][‘in’][‘here’].

First have a look at the demo to see what it does.

The Logic

SoapToArray is a small class that extends PHP extension SimpleXMLElement and implements JsonSerializable. It uses the extension classes to walk the input SOAP (or XML) to extract an array. The helper object, SoapNodeUtilities, provides an interface by which to specifically find nodes in the resulting array.

The Code and Structure

Using the objects is now simple and uncoupled from any code that needs to parse a SOAP or even any other XML response. As shown in the sample below and in the demo, the whole array is available or the utilities can be used to access a single node in the resulting array. It doesn’t matter how deeply nested it is (and you no longer have to care about paths,) it will find it. The XML namespaces (usually) sort out any sibling nodes with the same identifiers.

<?php
// Valid XML string $xml_response
$SoapToArray        = new SoapToArray($xml_response, LIBXML_NOCDATA);
$SoapNodeUtilities  = new SoapNodeUtilities();
$array_response     = $SoapToArray->toArray();

// Generally SOMENODE is a dynamic variable - or the whole array
//is available in $array_response, see demo
$data = $SoapNodeUtilities->findNode('SOMENODE', $array_response);

Both objects are now PHP unit testable, provide a common point in which to parse SOAP or XML, and can be called from any point in the system.

An aside, I would have preferred to inject the data into the public method instead of the constructor, but since it extends SimpleXMLElement which requires the data as the constructor.