2

I have been using a SOAP API in a work project (yay lucky me!), the WSDL is basically pointless as the body of the request is <any /> so I am having to generate the SOAP request rather than using the classmap option. I am using this way https://www.fischco.org/technica/2011/php-soap/ to generate an array of objects, otherwise, as it says in the post I was getting the <BOGUS> tags.

This is pretty much how I have done it for years, it also seems to be the way a lot of other people do it from what I can see for example http://www.mm-newmedia.de/2017/10/php-und-bipro-sich-wiederholende-elemente-im-soap-request/ and http://andrecatita.com/code-snippets/php-soap-repeated-element-name/

It all works fine but I realised I was still using the 7.3 docker image so upgraded to 7.4 and this no longer works, I just get the tag for the array but not the individual items, I think it is because of this change in 7.4.

Calling get_object_vars() on an ArrayObject instance will now always return the properties of the ArrayObject itself (or a subclass). Previously it returned the values of the wrapped array/object unless the ArrayObject::STD_PROP_LIST flag was specified.

Has anyone had this issue before? I spent all afternoon yesterday trying to figure it out but I am totally stumped

Here is an example client which demonstrates this behaviour

<?php

class Location
{
    public $Name;
}

// Overwrite the client so it doesn't actually make a request
class DebugSoapClient extends SoapClient
{
    public function __construct(string $wsdl, array $options = [])
    {
        $options = array_merge([
            'cache_wsdl'   => WSDL_CACHE_NONE,
            'exceptions'   => true,
            'features'     => SOAP_SINGLE_ELEMENT_ARRAYS,
            'soap_version' => SOAP_1_1,
            'trace'        => true,
        ], $options);

        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version, $one_way = 0): string
    {
        return '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitWorkItemResponse xmlns="http://example.com/webservices/DataServices/v2/ServicesAccess"><SubmitWorkItemResult></SubmitWorkItemResult></SubmitWorkItemResponse></soap:Body></soap:Envelope>';
    }
}

// Create an instance of the client
$namespace = 'http://example.com/webservices/DataServices/v2/ServicesAccess';
$client = new DebugSoapClient(__DIR__ . '/example.wsdl');
$client->__setSoapHeaders(
    new SoapHeader(
        $namespace,
        'AuthenticationHeader',
        ['Username' => 'Foo', 'Password' => 'Bar', 'ClientName' => 'Baz']
    )
);

// Build the body of the request
$locations = new ArrayObject();
foreach (['London', 'New York', 'Paris', 'Rome'] as $l) {
    $loc = new Location();
    $loc->Name = new SoapVar($l, XSD_STRING, '', '', 'Name', $namespace);

    $locations->append(new SoapVar($loc, SOAP_ENC_OBJECT, '', '', 'Location', $namespace));
}

$request = [
    'SourceData' => [
        'any' => new SoapVar($locations, SOAP_ENC_OBJECT, '', '', 'Locations', $namespace)
    ],
];

// Dispatch the request
$client->__soapCall('SubmitWorkItem', [$request]);

// Get the request body
$doc = new DomDocument('1.0');
$doc->loadXML($client->__getLastRequest());
$doc->formatOutput = true;

echo $doc->saveXML();

Here is a simplified WSDL for it

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://example.com/webservices/DataServices/v2/ServicesAccess" xmlns:s1="http://microsoft.com/wsdl/types/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://example.com/webservices/DataServices/v2/ServicesAccess" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
    <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Data Services</wsdl:documentation>
    <wsdl:types>
        <s:schema elementFormDefault="qualified" targetNamespace="http://example.com/webservices/DataServices/v2/ServicesAccess">
            <s:import namespace="http://microsoft.com/wsdl/types/" />
            <s:element name="SubmitWorkItem">
                <s:complexType>
                    <s:sequence>
                        <s:element minOccurs="1" maxOccurs="1" name="SourceData" type="tns:Source" />
                    </s:sequence>
                </s:complexType>
            </s:element>
             <s:complexType name="Source">
                <s:sequence>
                    <s:any />
                </s:sequence>
            </s:complexType>
            <s:element name="AuthenticationHeader" type="tns:AuthenticationHeader" />
            <s:complexType name="AuthenticationHeader">
                <s:sequence>
                    <s:element minOccurs="0" maxOccurs="1" name="Username" type="s:string" />
                    <s:element minOccurs="0" maxOccurs="1" name="Password" type="s:string" />
                    <s:element minOccurs="0" maxOccurs="1" name="ClientName" type="s:string" />
                </s:sequence>
                <s:anyAttribute />
            </s:complexType>
            <s:element name="SubmitWorkItemResponse">
                <s:complexType>
                    <s:sequence>
                        <s:element minOccurs="0" maxOccurs="1" name="SubmitWorkItemResult">
                            <s:complexType mixed="true">
                                <s:sequence>
                                    <s:any />
                                </s:sequence>
                            </s:complexType>
                        </s:element>
                    </s:sequence>
                </s:complexType>
            </s:element>
        </s:schema>
        <s:schema elementFormDefault="qualified" targetNamespace="http://microsoft.com/wsdl/types/">
            <s:simpleType name="guid">
                <s:restriction base="s:string">
                    <s:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" />
                </s:restriction>
            </s:simpleType>
        </s:schema>
    </wsdl:types>
    <wsdl:message name="SubmitWorkItemSoapIn">
        <wsdl:part name="parameters" element="tns:SubmitWorkItem" />
    </wsdl:message>
    <wsdl:message name="SubmitWorkItemSoapOut">
        <wsdl:part name="parameters" element="tns:SubmitWorkItemResponse" />
    </wsdl:message>
    <wsdl:message name="SubmitWorkItemAuthenticationHeader">
        <wsdl:part name="AuthenticationHeader" element="tns:AuthenticationHeader" />
    </wsdl:message>
    <wsdl:portType name="ServicesAccessSoap">
        <wsdl:operation name="SubmitWorkItem">
            <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">This method accepts a single XML data block for processing.&lt;br&gt;Result returned synchronously.</wsdl:documentation>
            <wsdl:input message="tns:SubmitWorkItemSoapIn" />
            <wsdl:output message="tns:SubmitWorkItemSoapOut" />
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="ServicesAccessSoap" type="tns:ServicesAccessSoap">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="SubmitWorkItem">
            <soap:operation soapAction="http://example.com/webservices/DataServices/v2/ServicesAccess/SubmitWorkItem" style="document" />
            <wsdl:input>
                <soap:body use="literal" />
                <soap:header message="tns:SubmitWorkItemAuthenticationHeader" part="AuthenticationHeader" use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:binding name="ServicesAccessSoap12" type="tns:ServicesAccessSoap">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="SubmitWorkItem">
            <soap12:operation soapAction="http://example.com/webservices/DataServices/v2/ServicesAccess/SubmitWorkItem" style="document" />
            <wsdl:input>
                <soap12:body use="literal" />
                <soap12:header message="tns:SubmitWorkItemAuthenticationHeader" part="AuthenticationHeader" use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap12:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="ServicesAccess">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Data Services</wsdl:documentation>
        <wsdl:port name="ServicesAccessSoap" binding="tns:ServicesAccessSoap">
            <soap:address location="https://test.example.com/uat/ServicesAccess.asmx" />
        </wsdl:port>
        <wsdl:port name="ServicesAccessSoap12" binding="tns:ServicesAccessSoap12">
            <soap12:address location="https://test.example.com/uat/ServicesAccess.asmx" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

This is the output in 7.3 and before

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/webservices/DataServices/v2/ServicesAccess">
  <SOAP-ENV:Header>
    <ns1:AuthenticationHeader>
      <ns1:Username>Foo</ns1:Username>
      <ns1:Password>Bar</ns1:Password>
      <ns1:ClientName>Baz</ns1:ClientName>
    </ns1:AuthenticationHeader>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <ns1:SubmitWorkItem>
      <ns1:SourceData>
        <ns1:Locations>
          <ns1:Location>
            <ns1:Name>London</ns1:Name>
          </ns1:Location>
          <ns1:Location>
            <ns1:Name>New York</ns1:Name>
          </ns1:Location>
          <ns1:Location>
            <ns1:Name>Paris</ns1:Name>
          </ns1:Location>
          <ns1:Location>
            <ns1:Name>Rome</ns1:Name>
          </ns1:Location>
        </ns1:Locations>
      </ns1:SourceData>
    </ns1:SubmitWorkItem>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This is the output 7.4 and later

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/webservices/DataServices/v2/ServicesAccess">
  <SOAP-ENV:Header>
    <ns1:AuthenticationHeader>
      <ns1:Username>Foo</ns1:Username>
      <ns1:Password>Bar</ns1:Password>
      <ns1:ClientName>Baz</ns1:ClientName>
    </ns1:AuthenticationHeader>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <ns1:SubmitWorkItem>
      <ns1:SourceData>
        <ns1:Locations/>
      </ns1:SourceData>
    </ns1:SubmitWorkItem>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
1
  • 1
    Have you tried instead of using new ArrayObject just using $locations = []; then adding like this $locations[] = $location; and replace SOAP_ENC_OBJECT by SOAP_ENC_ARRAY? Commented Aug 1, 2020 at 16:59

1 Answer 1

3

You just need to apply 1 change:

$request = [
    'SourceData' => [
        'any' => new SoapVar($locations->getArrayCopy(), SOAP_ENC_OBJECT, '', '', 'Locations', $namespace)
    ],
 ];
 // add this                           ^--------------^
Sign up to request clarification or add additional context in comments.

3 Comments

Jesus, how on earth did I miss that! Could have sworn I tried a plain old array but clearly not! Friday afternoon obviously not the best time to do an upgrade even on a local environment. 🤦‍♂️ Thanks
Not your fault, it worked perfectly fine on PHP < 7.4 - tested it :)
One of those situations where you get stuck on something and go round in circles I think... If I could have left it until Monday no doubt I would have figured it out eventually but it had been bugging me so much 😂

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.