There may be a time when you need to deal with passing polymorphic objects back and forth between PHP’s SoapClient and a SOAP web service. Things are nice and easy (work as expected) when you are retrieving these objects from a service. But, when it comes time to pass these objects back…well…it’s not as simple as one would hope.
For the following examples, we are going to assume that we are connecting to a service that has methods that return and accept an abstract type named “Animal”. Animal can be an instance of Dog or Cat.
- Animal
— Dog
— Cat
Now let’s assume that there are two methods in the service:
getAnimalById(Long id)
saveAnimal(Animal animal)
Here is a snippet of what the WSDL for these service methods and types would look like:
-
-
<xs:complexType name="getAnimalById">
-
<xs:sequence>
-
<xs:element name="id" type="xs:long" minOccurs="0" />
-
</xs:sequence>
-
</xs:complexType>
-
-
<xs:complexType name="getAnimalByIdResponse">
-
<xs:sequence>
-
<xs:element name="return" type="ns1:Animal" minOccurs="0" />
-
</xs:sequence>
-
</xs:complexType>
-
-
<xs:complexType name="saveAnimal">
-
<xs:sequence>
-
<xs:element name="animal" type="ns1:Animal" minOccurs="0" />
-
</xs:sequence>
-
</xs:complexType>
-
<xs:complexType name="saveAnimalResponse">
-
<xs:sequence />
-
</xs:complexType>
-
-
<xs:complexType name="Animal" abstract="true">
-
<xs:sequence />
-
</xs:complexType>
-
-
<xs:complexType name="Dog">
-
<xs:complexContent>
-
<xs:extension base="tns:Animal">
-
<xs:sequence />
-
</xs:extension>
-
</xs:complexContent>
-
</xs:complexType>
-
-
<xs:complexType name="Cat">
-
<xs:complexContent>
-
<xs:extension base="tns:Animal">
-
<xs:sequence />
-
</xs:extension>
-
</xs:complexContent>
-
</xs:complexType>
Now, using this information we are going to write our PHP classes which map to these service types:
-
-
abstract class Animal {}
-
class Dog extends Animal {}
-
class Cat extends Animal {}
Now, we will test out calling the service to try and get one of these implementing types:
-
-
// define our service to client class mapping
-
$classMap = array('Animal' => 'Animal',
-
'Dog' => 'Dog',
-
'Cat' => 'Cat');
-
-
// create client with classmap
-
$client = new SoapClient("http://my-service-url:8080/AnimalService?wsdl", array('classmap' => $classMap));
-
-
// get an animal
-
$result = $client->getAnimalById(1);
-
-
// what did we get?
-
var_dump($result);
Assuming that id:1 is supposed to return a Dog, we will see that this works as expected when the previous code is run:
-
-
object(stdClass)[42]
-
public 'return' =>
-
object(Dog)[43]
Now, we will see how passing a Cat object to the saveAnimal() method doesn’t work as expected:
-
-
// meow!
-
$cat = new Cat();
-
-
// oh-oh!!!
-
$client->saveAnimal(array('animal' => $cat));
Chances are that the service will return some sort of SoapFault exception like “Unable to create an instance of Animal…”.
The problem here is that by default PHP’s SoapClient does not automatically take the implementing Cat object and pass it to the service as a Cat. Instead, it only sees that saveAnimal() is expecting an Animal type, so that’s what it gives it. Luckily, there is a solution to this problem with a little extra code.
The solution is to manually create a SoapVar object to wrap around the object that we are trying to pass into the service method. The important things here are that you need pass it the object, the name of the concrete class (Cat), and the namespace that the Cat type belongs to within the WSDL service definition.
Here’s what the revised code may look like:
-
-
// meow!
-
$cat = new Cat();
-
-
// create our SoapVar wrapper
-
$catSoapVar = new SoapVar($cat, // the object to wrap
-
SOAP_ENC_OBJECT,
-
get_class($cat), // the concrete class name
-
'http://animals.lampjunkie.com' // namespace that Animal, Dog, Cat belong to (in WSDL)
-
);
-
-
// purr…
-
$client->saveAnimal(array('animal' => $catSoapVar));