Dan Walker
Dan Walker

Reputation: 464

Multiple SOAP Namespaces in PHP (solved in Python)

I am attempting to create this output in PHP:

    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:ns0="http://webservices.company.co.uk/AddressMatching" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://webservices.company.co.uk/ServiceBase/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
       <SOAP-ENV:Header/>
       <ns1:Body>
          <ns0:GetAvailableAddresses>
             <ns0:request>
                <ns2:UserCredentials>
                   <ns2:AgentID>123</ns2:AgentID>
                   <ns2:Password>PASSword</ns2:Password>
                   <ns2:Username>[email protected]</ns2:Username>
                </ns2:UserCredentials>
                <ns0:Address>
                   <ns0:PostCode>NG42DJ</ns0:PostCode>
                </ns0:Address>
             </ns0:request>
          </ns0:GetAvailableAddresses>
       </ns1:Body>
    </SOAP-ENV:Envelope>

This is fairly easy in Python, the class I'm using (suds) simply reads the wsdl file, and uses the namespaces correctly:

    from suds.client import Client

    client = Client('/the/path/to/AddressMatchingService.wsdl')

    creds = { 'AgentID': '123', 'Username': '[email protected]', 'Password': 'PASSword' }

    address = client.factory.create('Address')
    address.PostCode = "NG42DJ"

    address_request = client.factory.create('AvailableAddressesRequest')
    address_request.UserCredentials = creds
    address_request.Address = address

    request = client.service.GetAvailableAddresses(address_request)
    print request

There is no need to reference anything to do with namespaces, it simply works by reading the wsdl file and figuring it out. As you can see in the original XML above, the variables have namespaces and inherit where required, also note that the Body is in the ns1 namespace.

The closest I have got in PHP, is using a WSDL-to-PHP converter which generates a ton of classes based on functions within the file, but it seems to lose all sense of namespacing by doing this. The only way of working with it I can see so far is to modify the generated class files like this:

    // some code omitted...
    use SoapVar; // added by me

    // this is declared in AddressMatchingService namespace
    class Credentials
    {

        public $AgentID = null;
        public $Username = null;
        public $Password = null;

        public function __construct($AgentID, $Username, $Password)
        {
          //$this->AgentID = $AgentID;
          $this->AgentID = new SoapVar($AgentID, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");

          //$this->Username = $Username;
          $this->Username = new SoapVar($Username, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");

          //$this->Password = $Password;
          $this->Password = new SoapVar($Password, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");
        }
    }



    $wsdl = base_path() . '/resources/wsdl/AddressMatchingService.wsdl';
    $soap = new SoapClient($wsdl, array('trace'=>1));

    $creds = new AddressMatchingService\Credentials('123', '[email protected]','PASSword');
    $address = new AddressMatchingService\Address('NG42DJ');

    $request = array('request' => $request);
    $request = new SoapVar($request, SOAP_ENC_OBJECT);

    $response = $soap->__soapCall("GetAvailableAddresses", array('request' => $request));

Which gets me close-ish:

    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://webservices.company.co.uk/ServiceBase/" xmlns:ns2="http://webservices.company.co.uk/AddressMatching">
    <SOAP-ENV:Body>
        <ns2:GetAvailableAddresses>
            <request>
                <Address>
                    <PostCode>NG42DJ</PostCode>
                </Address>
                <ns1:UserCredentials>
                    <ns1:AgentID>123</ns1:AgentID>
                    <ns1:Password>PASSword</ns1:Password>
                    <ns1:Username>[email protected]</ns1:Username>
                </ns1:UserCredentials>
                <UPRN/>
            </request>
        </ns2:GetAvailableAddresses>
    </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>

But if it's so easy in Python, I thought I might be doing something wrong. Specifying the namespace in each class seems a pain, and I still don't know if it's possible to change the namespace of the Body from 'SOAP-ENV' to ns1. I would like to avoid handcrafting XML if I could avoid it, as there's so many functions and variables in the WSDL, I'd ideally like to make it work as efficiently as the Python suds library does!

Upvotes: 0

Views: 655

Answers (1)

ThW
ThW

Reputation: 19492

SOAP-ENV or ns1 are not the namespaces, but aliases for them. The actual namespaces are the values in the xmlns:* attributes. In your first example, they resolve both to the same namespace.

  • SOAP-ENV:Envelope -> {http://schemas.xmlsoap.org/soap/envelope/}Envelope
  • SOAP-ENV:Header -> {http://schemas.xmlsoap.org/soap/envelope/}Header
  • ns1:Body -> {http://schemas.xmlsoap.org/soap/envelope/}Body

The alias does not change the meaning. Having different aliases for the same namespace could be considered bad, because it lowers the readability.

Second, just because the default API only has some calls with lots of specific arguments does not mean that you have to use them directly. Think about using helper methods or loops to avoid repeating yourself:

class Credentials
{
    private namespaceUri = "http://webservices.company.co.uk/ServiceBase/";

    public $AgentID = null;
    public $Username = null;
    public $Password = null;

    public function __construct($AgentID, $Username, $Password)
    {
      $this->AgentID = $this->createSoapValue($AgentID);
      $this->Username = $this->createSoapValue($Username);
      $this->Password = $this->createSoapValue($Password);
    }

    private function createSoapValue($value) {
      return new new SoapVar($value, null, null, null, null, $this->namespaceUri);
    }
}

Last check the request element. I think you forgot the namespace for it.

Upvotes: 1

Related Questions