23
votes

I'm trying to call a SOAP method using PHP.

Here's the code I've got:

$data = array('Acquirer' =>
  array(
    'Id' => 'MyId',
    'UserId' => 'MyUserId',
    'Password' => 'MyPassword'
  ));
$method = 'Echo';
$client = new SoapClient(NULL,
           array('location' => 'https://example.com/ExampleWebServiceDL/services/ExampleHandler', 
           'uri' => 'http://example.com/wsdl', 'trace' => 1));
$result = $client->$method($data);

Here's the request it creates:

  <?xml version="1.0" encoding="UTF-8"?>
  <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/wsdl" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <SOAP-ENV:Body>
      <ns1:Echo>
        <param0 xsi:type="ns2:Map">
          <item>
            <key xsi:type="xsd:string">Acquirer</key>
            <value xsi:type="ns2:Map">
              <item>
                <key xsi:type="xsd:string">Id</key>
                <value xsi:type="xsd:string">mcp</value>
              </item>
              <item>
                <key xsi:type="xsd:string">UserId</key>
                <value xsi:type="xsd:string">tst001</value>
              </item>
              <item>
                <key xsi:type="xsd:string">Password</key>
                <value xsi:type="xsd:string">test</value>
              </item>
            </value>
          </item>
        </param0>
      </ns1:Echo>
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

And here's what I want the request to look like:

  <?xml version="1.0" encoding="UTF-8"?>
  <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/wsdl" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <SOAP-ENV:Body>
      <Echo>
        <Acquirer>
          <Id>MyId</Id>
          <UserId>MyUserId</UserId>
          <Password>MyPassword</Password>
        </Acquirer>
      </Echo>
    </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>
2
This cannot be solved without the complete WSDL info available. The PHP SoapClient actively parses and uses the WSDL to put the function arguments into the right places. Without the WSDL loaded, it is nearly impossible to create something that works. - Sven
@Sven - that's very helpful, though it didn't fully solve my problem. Can you post it as an answer? If nobody else responds I'll at least accept that since it helped me forward somewhat. - greggles
No thanks, you got a better answer which might deserve the bounty. - Sven
@Sven - well, your answer got me to a solution even though it didn't directly answer my question ;) thanks to both Sven and Matthijs van den Bos for your help! - greggles

2 Answers

52
votes

There are a couple of ways to solve this. The least hackiest and almost what you want:

$client = new SoapClient(
    null,
    array(
        'location' => 'https://example.com/ExampleWebServiceDL/services/ExampleHandler',
        'uri' => 'http://example.com/wsdl',
        'trace' => 1,
        'use' => SOAP_LITERAL,
    )
);
$params = new \SoapVar("<Acquirer><Id>MyId</Id><UserId>MyUserId</UserId><Password>MyPassword</Password></Acquirer>", XSD_ANYXML);
$result = $client->Echo($params);

This gets you the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/wsdl">
    <SOAP-ENV:Body>
        <ns1:Echo>
            <Acquirer>
                <Id>MyId</Id>
                <UserId>MyUserId</UserId>
                <Password>MyPassword</Password>
            </Acquirer>
        </ns1:Echo>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

That is almost exactly what you want, except for the namespace on the method name. I don't know if this is a problem. If so, you can hack it even further. You could put the <Echo> tag in the XML string by hand and have the SoapClient not set the method by adding 'style' => SOAP_DOCUMENT, to the options array like this:

$client = new SoapClient(
    null,
    array(
        'location' => 'https://example.com/ExampleWebServiceDL/services/ExampleHandler',
        'uri' => 'http://example.com/wsdl',
        'trace' => 1,
        'use' => SOAP_LITERAL,
        'style' => SOAP_DOCUMENT,
    )
);
$params = new \SoapVar("<Echo><Acquirer><Id>MyId</Id><UserId>MyUserId</UserId><Password>MyPassword</Password></Acquirer></Echo>", XSD_ANYXML);
$result = $client->MethodNameIsIgnored($params);

This results in the following request XML:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <Echo>
            <Acquirer>
                <Id>MyId</Id>
                <UserId>MyUserId</UserId>
                <Password>MyPassword</Password>
            </Acquirer>
        </Echo>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Finally, if you want to play around with SoapVar and SoapParam objects, you can find a good reference in this comment in the PHP manual: http://www.php.net/manual/en/soapvar.soapvar.php#104065. If you get that to work, please let me know, I failed miserably.

12
votes

First off, you have to specify you wish to use Document Literal style:

$client = new SoapClient(NULL, array(
    'location' => 'https://example.com/path/to/service',
    'uri' => 'http://example.com/wsdl',
    'trace' => 1,
    'use' => SOAP_LITERAL)
);

Then, you need to transform your data into a SoapVar; I've written a simple transform function:

function soapify(array $data)
{
        foreach ($data as &$value) {
                if (is_array($value)) {
                        $value = soapify($value);
                }
        }

        return new SoapVar($data, SOAP_ENC_OBJECT);
}

Then, you apply this transform function onto your data:

$data = soapify(array(
    'Acquirer' => array(
        'Id' => 'MyId',
        'UserId' => 'MyUserId',
        'Password' => 'MyPassword',
    ),
));

Finally, you call the service passing the Data parameter:

$method = 'Echo';

$result = $client->$method(new SoapParam($data, 'Data'));