This post is pretty much a revamp of a previous post called “Working with X-Fire soap services and inheritance in PHP“. That title was a bit misleading, and not really that good when it came to providing a sample.

Let’s say we have a SOAP method called createCustomer(Customer c), and we have a base class called Customer, which has to subclasses, Person and Organization. Person has firstname, lastname, while Organization as orgname and orgid. Now, the createCustomer call accepts a object of the class Customer, and any derived classes.

When retrieving a Person / Organization over SOAP, PHP automatically creates instances of the correct classes, but when calling createCustomer, passing a Person / Organization it breaks. Out object is sent as a Customer, but with Person or Organization fields added. The SOAP server expects to find a xsi:type for the object we are sending to tell what kind of Customer it is. It seems like PHP does not set this itself (maybe it should?).

I spent quite some time looking for info on how to specify the xsi:type for the objects, and I finally came across SoapVar.

I created a base class which the SOAP classes extended. A method called pack is responsible for setting xsi:type.

(I’m very aware that my pasted code looks like a mess in this blog. I will fix that ASAP).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
class BaseClass{
  private  $namespace = "http://model.api.domain.com"; // from your WSDL
  protected function pack($obj){
    $class_name = get_class($obj);
    $namespace = "http://model.api.domain.com";
    $pack = new SoapVar($obj, XSD_STRING, "$class_name", $namespace);
    return $pack;
  }
}
 
class createCustomer extends BaseClass{
  public function setCustomer($customer){
    $this->customer = $this->pack($customer);
  }
}
 
$customer = new Person();
$customer->setName("John Doe");
 
$request = new createCustomer();
$request->setAccountID(123);
$request->setCustomer($customer);
 
$client->createCustomer($request);

That fixed the problems for me atleast.

PS: If your SOAP classes are prefixed you’ll need to strip the prefix in class_name when creating the SoapVar.

Related posts:

[tags]PHP, SOAP[/tags] 

When working against complex soap servers from PHP it’s generally a nice thing to be able to act on exceptions and not just provide the error message. I’ve been working against X-Fire web services for the last few months, and every time an exception is thrown it’s returned as a SoapFault with the actual exception name as property in the detail field. I wanted proper exceptions which I could do something useful with, so I poked around and came up with something which fixes this. The basic idea is to catch the soap fault exception, parse it for information and build a new exception based on the name which is retrieved. The code is a bit gritty since the actual name of the exception is a field, but that’s nothing that reflection can’t solve. The doRequest method is the method used to call the web service methods. This is a part of my soap client class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* Do the soap request
*
*/
protected function doRequest($name, $args = array()) {
  try {
    $response = $this->__soapCall($name, $args, $this->options);
    return $response;
  }
  catch(Exception $e) {
    $newEx = $this->buildException($e);
    throw $newEx;
  }
}
 
 
/**
* parses a soap fault and returns the error code and message
* used by buildException
*
* @param SoapFault $e
* @return array
*/
protected function parseSoapFault(SoapFault $e) {
  $pos = strpos($e->faultstring, ':');
  $code = "";
  if($pos) {
    $code = substr($e->faultstring, 0, $pos);
    $message = trim(substr($e->faultstring, $pos+1, strlen($e->faultstring)));
  } else {
    $message = $e->faultstring;
  }
  return array($code, $message);
}
 
 
/**
* This method takes a SoapFault and builds a proper exception from it
*
* The SoapFault can hold a server side exception name in it's detail field. This
* method takes a peek to see if that field exists, and if it does the name of the
* field is extracted via reflection. A new exception is created of the class that
* was just retrieved and the message and code is set.
*
* The message and error code is extracted from the soap fault.
* @param unknown_type $e
* @return unknown
*/
protected function buildException($e){
  list($code, $message) = $this->parseSoapFault($e);
  if (!isset($e->detail)){ // No exception found
    $ex = new GenericException($message,$code);
    return $ex;
  }
 
  // get the actual name of the exception
  $reflectionObject = new ReflectionObject($e->detail);
  $properties = $reflectionObject->getProperties();
  $exceptionName = $properties[0]->name;
 
  $exception = new $exceptionName($message,$code);
  return $exception;
}

I’ve been working with X-fire / ageis services in SOAP now for a while, and there’s been only one issue I’ve not been able to solve. Arrays.

When the back-end send an array with item count > 1 everything works fine. The problems start when there is only one item in the array. The array is collapsed and instead there is a object that is the only item. At first I thought this was the java back-end that screwed up, but after inspecting the SOAP messages from NGREP I found out that it’s PHP that screws up.

I’ve prepared a short example called lookupPerson which takes a username and returns a Person with his related cars.

Consider the first sample, where the person has three cars:

SOAP request:
<pre line="1" lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://api.service.com/temp/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="urn:Service">
<SOAP-ENV:Header>
<ns2:RemoteAddress xsi:type="SOAP-ENC:string">127.0.0.1</ns2:RemoteAddress>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:lookupPerson>
<ns1:in0>john</ns1:in0>
</ns1:lookupPerson>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

</pre>

SOAP response:
<pre line="1" lang="xml">

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<lookupPersonResponse xmlns="http://api.service.com/temp/">
<out xmlns="http://api.service.com/temp/">
<username xmlns="http://users.model.service.com">john</username>
<cars xmlns="http://users.model.service.com">
<Car>
<car>HUMMER</car>
</Car>
<Car>
<car>BMW</car>
</Car>
</cars>
</out>
</lookupPersonResponse>
</soap:Body>
</soap:Envelope>

</pre>

Now that looks pretty much right. Lets take a look at a var_dump of the returned object:

object(lookupPersonResponse)#571 (1) {
["out"]=> object(Person)#570 (2) {
["username"]=> string(11) "john"
["cars"]=> object(stdClass)#573 (1) {
["Car"]=> array(2) {
[0]=> object(Car)#572 (1) {
["car"]=> string(17) "BMW"
}
[1]=> object(Car)#574 (1) {
["car"]=> string(19) "HUMMER"
}
}
}
}
}

Yep. Everything is correct. The class map works as intended. Now, lets get over to the bad stuff. The following output is the same call, but this time John has lost his Hummer.

SOAP response:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<lookupPersonResponse xmlns="http://api.service.com/temp/">
<out xmlns="http://api.service.com/temp/">
<username xmlns="http://users.model.service.com">john</username>
<cars xmlns="http://users.model.service.com">
<Car>
<car>BMW</car>
</Car>
</cars>
</out>
</lookupPersonResponse>
</soap:Body>
</soap:Envelope>

Well, the SOAP response is pretty much like the first one.

PHP object:

object(lookupPersonResponse)#571 (1) {
["out"]=> object(Person)#570 (2) {
["username"]=> string(11) "john"
["cars"]=> object(stdClass)#573 (1) {
["Car"]=> object(Car)#572 (1) {
["car"]=> string(17) "BMW"
}
}
}
}

Do you see the difference here? Car is pointing to the car object instance instead of an array. This lays the path for some ass logic work for everything that returns an array.

Lets take a look at the relevant parts of the WSDL:

<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element minOccurs="0" name="username" nillable="true" type="xsd:string"/>
<xsd:element minOccurs="0" name="cars" nillable="true" type="ns1:ArrayOfCar"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ArrayOfCar">
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="Car" nillable="true" type="ns1:Car"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Car">
<xsd:sequence>
<xsd:element minOccurs="0" name="car" nillable="true" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="lookupPersonResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="1" name="out" nillable="true" type="ns1:Person"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>

According to WSDL standard this is legal and should work, but I dont think PHP is too happy about it. I’ve tried doing similar stuff with a service that describes arrays as :


<complexType name="ArrayOf_soapenc_string">
<complexContent>
<restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType" wsdl:arrayType="soapenc:string[]"/>
</restriction>
</complexContent>
</complexType>

This way seems to work just fine, so I fear that the problem has something to do with X-Fire and ageis. I’m very interested in some hints on how to get this running properly.

A while ago we started using X-Fire for soap services. The new soap services we created used inheritance in the returned classes, which wasn’t really that straight forward to implement in PHP. Let’s say we have a abstract class Person, which holds whatever common data we need to have about a person. In addition there are two sub-classes of Person called Runner and Madman. The runner holds info about max speed while the madman has a insanity score (I rock at examples!).

I set up three classes in PHP called MyPerson, MyMadman and MyRunner. MyMadman and MyRunner inherits MyPerson. The class map was set up to map Person to MyPerson, Madman to MyMadman and Runner to MyRunner. This worked like a charm for retriving data that was sent from the backend. A madman came over the wire as a Person with xsi:type set to Madman. The problems started when I was supposed to send a Madman back over soap. Let’s call the method GiveCake(Person p, string cake). The soap side expected any kind of Person to arrive. First I tired just sending a Madman with a delicious cake title, but to my surprise that didn’t work at all. The problem is that the object was sent as a Person with the Madman taggs added, but with no xsi:type to tell the soap server that this in fact was a Madman. After looking all over for hints on how make this work I came across SoapVar. Thef following one-liner solved the problems: $soapObject = new SoapVar($obj, XSD_STRING, “$class_name”, $namespace). Class name is the xsi:tag here.