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 交替优化波束形成和ris反射角使保密速率最大化
  • ¥15 树莓派与pix飞控通信
  • ¥15 自动转发微信群信息到另外一个微信群
  • ¥15 outlook无法配置成功
  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程