为什么PHP 7.x SoapServer为非重复数组返回XML引用(例如href =“#ref1”)?

时间:2017-09-28 23:45:21

标签: php xml soap soapserver

使用PHP的内置SoapServer来返回包含两个不同数组的响应时出现问题。 PHP认为我的fruitvegetables数组是重复的(它们不是)。响应使用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认为fruitsvegetables完全相同,导致它返回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);

更新

  • 此问题PHP SoapClient creating XML references for identical elements, makes it unacceptable for service的解决方案不起作用。我的元素并不完全相同。如果我的元素相同,我就可以使用XML引用。
  • 更改WSDL以使其包含两个单独的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中不起作用。

2 个答案:

答案 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,并查看其修复时间。