douluanzhao6689 2017-09-28 23:45
浏览 97

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

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.

  • 写回答

1条回答 默认 最新

  • doushi8231 2017-11-23 16:16
    关注

    Your xsd seems not exactly how it needs to be. To define one element as a sequence (array), you need something like this inside Baz definition:

    <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>
    

    Reference

    评论

报告相同问题?

悬赏问题

  • ¥15 微信公众号自制会员卡没有收款渠道啊
  • ¥15 stable diffusion
  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘
  • ¥15 perl MISA分析p3_in脚本出错
  • ¥15 k8s部署jupyterlab,jupyterlab保存不了文件
  • ¥15 ubuntu虚拟机打包apk错误
  • ¥199 rust编程架构设计的方案 有偿