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 ue5 .3之前好好的现在只要是激活关卡就会崩溃
  • ¥50 MATLAB实现圆柱体容器内球形颗粒堆积
  • ¥15 python如何将动态的多个子列表,拼接后进行集合的交集
  • ¥20 vitis-ai量化基于pytorch框架下的yolov5模型
  • ¥15 如何实现H5在QQ平台上的二次分享卡片效果?
  • ¥15 python爬取bilibili校园招聘网站
  • ¥30 求解达问题(有红包)
  • ¥15 请解包一个pak文件
  • ¥15 不同系统编译兼容问题
  • ¥100 三相直流充电模块对数字电源芯片在物理上它必须具备哪些功能和性能?