phpsoapsoap-clientxsi

PHP soap client add xsi:type to node


I made a soap client with the php Soap extension and want to call a server. The XML is very complex and some NS won't be added automatically, I add them with SoapVar() But I have a problem with some node, my element should like this

<ns1:Element xsi:type="ns2:MyType">

And I try it with this

$request[$elem] = new \SoapVar($myData, SOAP_ENC_OBJECT, 'MyType', 'myNS2', 'Element', 'myNS1');

But in the request I am getting this without the xsi:type, if I change the type or ns it will be added but I need exactly this. <ns1:Element>Data</ns1:Element>

What could be the problem?


Solution

  • You have several options to solve your issue.

    The classmap option

    Within the options when initializing your SoapClient class, there is a classmap option. This array takes the types, that were defined in the wsdl as keys with the corresponding classes as values.

    Think of the following wsdl ...

    <complexType name="PersonIdentity">
      <sequence>
        <element name="Id" type="xsd:string" />
      </sequence>
    </complexType>
    <complexType name="Person">
      <complexContent extends="PersonIdentity">
        <sequence>
          <element name="Name" type="xsd:string" />
          <element name="FirstName" type="xsd:string" />
        </sequence>
      </complexContent>
    </complexType>
    

    With this information you can create two classes ...

    <?php
    
    class PersonIdentity {
      public string $Id;
    }
    
    class Person extends PersonIdentity {
      public string $Name;
      public string $FirstName;
    }
    

    You have to know, that soap is pretty easy to handle, when staying in the oop context. Just deal with objects you have created from the wsdl and your life will be pretty easy. The only thing you have to do is mentioning your data objects in the classmap option of your soap client.

    <?php
    
    $client = new \SoapClient($wsdl, [
        ...
        'classmap' => [
            'PersonIdentity' => PersonIdentity::class,
            'Person' => Person::class,
        ],
        ...
    ]);
    

    Then just call your function on your soap client instance. Your wsdl operation contract should look like the following snippet.

    <complexType name="checkPerson">
     <sequence>
      <element name="Person" type="PersonIdentity"/>
     </sequence>
    </complexType>
    

    And then just call it in php ...

    $person = new Person();
    $person->Id = '42';
    $person->Name = 'Maaß';
    $person->FirstName = 'Marcel';
    
    $client->checkPerson($person);
    

    The soap function call should have that xsi:type attribute automatically.

    The typemap option

    You can use a typemap when initialising the SoapClient class. You can specify various options in the constructor. Among others also a typemap option.

    <?php
    ...
    $client = new \SoapClient($wsdl, [
        'typemap' => [
            [
                'type_name' => 'xml_element_type_name',
                'type_ns' => 'namspace_uri',
                'from_xml' => function(string $xml): object {
                    // converting the string to an object from responses
                },
                'to_xml' => function(object $node): string {
                    // converting the object to an xml string for requests
                }, 
            ],
        ],
    ]);
    

    For every type you can add callback functions to decode from xml and encode to xml. You can define your proper types in this callbacks.

    The SoapClient::doRequest() method

    The SoapClient::doRequest() method is the last possibility to manipulate the XML that has already been compiled at this point. One can easily override it in your own SoapClient implementation.

    <?php
    
    declare(strict_types=1);
    
    namespace Marcel\Soap;
    
    use SoapClient;
    
    class MySoapClient extends SoapClient
    {
        public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string 
        {
            // $request contains the full xml content which you want to send
            // if your type attribute isn 't set at this moment, you can edit
            // the xml here with the DOMXpath and DOMDocument classes.
        }
    }
    

    Reasons for failing

    Soap and the implementation in PHP are sometimes a little misleading. Sometimes a malformed or uncomplete wsdl is the reason for failing. Sometimes some W3C delays on loading widely used type definitions you havent stored on your server are the reasons for failing. The soap lib implementation in php is very strict. The smallest errors in the wsdl will lead into an error. You have to work very precisely to get a complex web service to work. Sometimes you can't manipulate the WSDL data. Then you have to code around the mistakes that others have already made long before you. Soap has never been really easy with PHP.