使用PHP的内置SoapServer
来返回包含两个不同数组的响应时出现问题。 PHP认为我的fruit
和vegetables
数组是重复的(它们不是)。响应使用Wrapper
类,使用PHP的__get()
魔术重载方法,这似乎是问题的一部分。
我的代码在PHP版本5.3,5.4,5.5和5.6中运行良好,并生成正确的SOAP XML响应:
<?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>
PHP 7.0,7.1和7.2RC2中的完全相同的代码产生以下意外的XML,其中包含vegetables
集合中的引用,指向fruit
集合:
<?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>
我的问题是,为什么PHP 7.x认为fruits
和vegetables
完全相同,导致它返回XML引用?为什么7.x版本中的行为会发生变化?如何继续使用包装类和__get()
方法并获得与早期版本的PHP相同的响应?
这是我可验证的示例,自包含在一个PHP文件中。它可以直接从命令行运行(不需要Web服务器):
$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);
更新
StringArray
结构不起作用我尝试将“item”添加到我的数组中(它不起作用):
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;
}
我可以通过使用具有item
属性的对象来修复PHP 7.x中的问题,如下所示:
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;
}
但是我对此并不满意。我仍然不知道为什么使用数组在PHP 7.x中不起作用。
答案 0 :(得分:0)
你的xsd似乎并不完全是它需要的。要将一个元素定义为序列(数组),您需要在Baz定义中使用类似的东西:
<xs:element name="fruits">
<xs:complexType>
<xs:sequence>
<xs:element name="fruit" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:element name="vegetables">
<xs:complexType>
<xs:sequence>
<xs:element name="vegetable maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
{{3}}
答案 1 :(得分:0)
这是一个老错误,已在2018年底修复,但不确定其发布的PHP版本。基本上所有引用的对象都会触发此错误:
https://bugs.php.net/bug.php?id=50675
PHP 7.x比旧版本有更积极的优化-我猜是因为它在代码中不再使用,并且第二个数组更小,所以它可以重复使用数组的内存。因此,对于XML编码器,它看起来像是对同一对象的引用。
一个临时解决方案似乎是强制转换为一个对象,并明确克隆它(或者以其他方式弄乱您已经发现的变量):
$fruits = clone (object)array( "item" => array("apple", "orange", "banana"));
否则,您可以尝试使用最新的/ beta版本的PHP,并查看其修复时间。