I have a problem using PHP's built-in SoapServer
to return a response containing two different arrays. PHP thinks my fruit
and vegetables
arrays are duplicates (they are not). The response uses a Wrapper
class making use of PHP's __get()
magic overloading method, which seems to be part of the problem.
My code works perfectly fine in PHP versions 5.3, 5.4, 5.5 and 5.6 and generates a correct SOAP XML response:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/">
<SOAP-ENV:Body>
<ns1:fooResponse>
<result>
<fruit>
<item>apple</item>
<item>orange</item>
<item>banana</item>
</fruit>
<vegetables>
<item>carrots</item>
<item>broccoli</item>
</vegetables>
</result>
</ns1:fooResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The exact same code in PHP versions 7.0, 7.1 and 7.2RC2 produces the following unexpected XML, which contains a reference in the vegetables
collection, pointing back to the fruit
collection:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/">
<SOAP-ENV:Body>
<ns1:fooResponse>
<result>
<fruit id="ref1">
<item>apple</item>
<item>orange</item>
<item>banana</item>
</fruit>
<vegetables href="#ref1"/>
</result>
</ns1:fooResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
My questions are, why does PHP 7.x think fruits
and vegetables
are exactly the same, causing it to return an XML reference? Why does the behavior change in the 7.x versions? How can I continue using a wrapper class and the __get()
method and achieve the same response as earlier versions of PHP?
Here is my verifiable example, self-contained in one PHP file. It can be run directly from the command line (no web server required):
$wsdl = <<<WSDL
<?xml version="1.0" encoding="utf-8"?>
<definitions name="SoapArrayTest"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://test-uri/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://test-uri/"
>
<types>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://test-uri/">
<complexType name="Baz">
<sequence>
<element name="fruit" type="tns:StringArray"/>
<element name="vegetables" type="tns:StringArray"/>
</sequence>
</complexType>
<element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="BazElement" type="tns:Baz"/>
<complexType name="StringArray">
<sequence>
<element name="item" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="StringArrayElement" type="tns:StringArray"/>
</schema>
</types>
<message name="fooRequest">
</message>
<message name="fooResponse">
<part name="result" type="tns:Baz"/>
</message>
<portType name="TestPortType">
<operation name="foo">
<input message="tns:fooRequest"/>
<output message="tns:fooResponse"/>
</operation>
</portType>
<binding name="TestBinding" type="tns:TestPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="foo">
<soap:operation soapAction="#foo" style="rpc"/>
<input />
<output >
<soap:body parts="result" use="literal" namespace="http://test-uri/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<service name="TestService">
<port name="TestPort" binding="tns:TestBinding">
<soap:address location="http://example.com"/>
</port>
</service>
</definitions>
WSDL;
class Wrapper
{
private $object;
public function __construct($object)
{
$this->object = $object;
}
public function __get($property)
{
$value = $this->object->$property;
return $value;
}
}
function foo()
{
$baz = new stdClass();
$arr1 = array( "apple", "orange", "banana");
$baz->fruit = $arr1;
$arr2 = array("carrots", "broccoli");
$baz->vegetables = $arr2;
$bar = new Wrapper($baz);
return $bar;
}
$fname = tempnam (__DIR__, "wsdl");
$f = fopen($fname,"w");
fwrite($f,$wsdl);
fclose($f);
$server = new SoapServer($fname, array('cache_wsdl' => WSDL_CACHE_NONE, 'soap_version' => SOAP_1_1));
$server->addFunction("foo");
$soapRequest = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="blah" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:foo>
</ns1:foo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;
$server->handle($soapRequest);
Updates:
- The solution to this similar question PHP SoapClient creating XML references for identical elements, makes it unacceptable for service doesn't work. My elements are not identical. If my elements were identical I would be OK with the XML references.
- Changing the WSDL so that it contains two separate
StringArray
structures does not work -
I tried adding 'item' to my arrays like this (it didn't work):
function foo() { $baz = new stdClass(); $fruits = array( "item" => array("apple", "orange", "banana")); $baz->fruit = $fruits; $veggies = array("item" => array("carrots", "broccoli")); $baz->vegetables = $veggies; $bar = new Wrapper($baz); return $bar; }
-
I can fix the issue in PHP 7.x by using objects with
item
property like this:function foo() { $baz = new stdClass(); $fruits = new stdClass(); $fruits->item = array("apple", "orange", "banana"); $baz->fruit = $fruits; $veggies = new stdClass(); $veggies->item = array("carrots", "broccoli"); $baz->vegetables = $veggies; $bar = new Wrapper($baz); return $bar; }
However I am not satisfied with this. I still don't know why using arrays won't work in PHP 7.x.