18

Is there such a beastie? The simple SOAP client that ships with PHP does not understand multi-part messages. Thanks in advance.

olefevre
  • 4,097
  • 3
  • 23
  • 24

7 Answers7

13

The native PHP SoapClient class does not support multipart messages (and is strongly limited in all WS-* matters) and I also I think that neither the PHP written libraries NuSOAP nor Zend_Soap can deal with this sort of SOAP messages.

I can think of two solutions:

  • extend the SoapClient class and overwrite the SoapClient::__doRequest() method to get hold of the actual response string which you can then parse at your whim.

    class MySoapClient extends SoapClient
    {
        public function __doRequest($request, $location, $action, $version, $one_way = 0)
        {
            $response = parent::__doRequest($request, $location, $action, $version, $one_way);
            // parse $response, extract the multipart messages and so on
        }
    }
    

    This could be somewhat tricky though - but worth a try.

  • use a more sophisticated SOAP client library for PHP. The first and only one that comes into my mind is WSO2 WSF/PHP which features SOAP MTOM, WS-Addressing, WS-Security, WS-SecurityPolicy, WS-Secure Conversation and WS-ReliableMessaging at the cost of having to install a native PHP extension.

Stefan Gehrig
  • 82,642
  • 24
  • 155
  • 189
5

Even though this answer has been given a lot here already, I have put together a general solutions, that keeps in mind, that the XML can come without the wrapper.

class SoapClientExtended extends SoapClient
{
    /**
     * Sends SOAP request using a predefined XML
     *
     * Overwrites the default method SoapClient::__doRequest() to make it work
     * with multipart responses.
     *
     * @param string $request      The XML content to send
     * @param string $location The URL to request.
     * @param string $action   The SOAP action. [optional] default=''
     * @param int    $version  The SOAP version. [optional] default=1
     * @param int    $one_way  [optional] ( If one_way is set to 1, this method
     *                         returns nothing. Use this where a response is
     *                         not expected. )
     *
     * @return string The XML SOAP response.
     */
    public function __doRequest(
        $request, $location, $action, $version, $one_way = 0
    ) {
        $result = parent::__doRequest($request, $location, $action, $version, $one_way);

        $headers = $this->__getLastResponseHeaders();

        // Do we have a multipart request?
        if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
            // Make all line breaks even.
            $result = str_replace("\r\n", "\n", $result);

            // Split between headers and content.
            list(, $content) = preg_split("#\n\n#", $result);
            // Split again for multipart boundary.
            list($result, ) = preg_split("#\n--#", $content);
        }

        return $result;
    }
}

This only works if you initialize the SoapClientExtended with the option trace => true.

func0der
  • 2,192
  • 1
  • 19
  • 29
3

Using S. Gehrig second idea worked just fine here.

In most cases, you have just a single message packed into a MIME MultiPart message. In those cases a "SoapFault exception: [Client] looks like we got no XML document" exception is thrown. Here the following class should do just fine:

class MySoapClient extends SoapClient
{
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
    {
        $response = parent::__doRequest($request, $location, $action, $version, $one_way);
        // strip away everything but the xml.
        $response = preg_replace('#^.*(<\?xml.*>)[^>]*$#s', '$1', $response);
        return $response;
    }
}
Pierre Spring
  • 10,525
  • 13
  • 49
  • 44
3

Follow the advice of rafinskipg from the PHP documentation:

Support for MTOM addign this code to your project:

<?php 
class MySoapClient extends SoapClient
{
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
    {
        $response = parent::__doRequest($request, $location, $action, $version, $one_way);
        // parse $response, extract the multipart messages and so on

        //this part removes stuff
        $start=strpos($response,'<?xml');
        $end=strrpos($response,'>');    
        $response_string=substr($response,$start,$end-$start+1);
        return($response_string);
    }
}

?>

Then you can do this

<?php
  new MySoapClient($wsdl_url);
?>
Wes Grant
  • 829
  • 7
  • 13
  • Even though I do not like your code style at all, you are my hero with this explicit code example :) Worked like charm. Though I had to tweak it a litte. I'll put it in a new answer. – func0der Nov 29 '16 at 10:34
2

Just to add more light to previous suggested steps. You must be getting response in following format

    --uuid:eca72cdf-4e96-4ba9-xxxxxxxxxx+id=108
Content-ID: <http://tempuri.org/0>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body> content goes here </s:Body></s:Envelope>
--uuid:c19585dd-6a5a-4c08-xxxxxxxxx+id=108--

Just use following code (I am not that great with regex so using string functions)

 public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
    $response = parent::__doRequest($request, $location, $action, $version, $one_way);
    // strip away everything but the xml.
    $response = stristr(stristr($response, "<s:"), "</s:Envelope>", true) . "</s:Envelope>";
return $response;
}
Pradeep Kumar Mishra
  • 10,839
  • 4
  • 25
  • 26
1

A little bit more simple IMHO

class SoapClientUnwrappedXml extends SoapClient
{

    const SOAP_ENVELOPE_REGEXP = '/^<soap:Envelope[^>]*>(.*)<\/soap:Envelope>/m';

    /**
     * Sends SOAP request using a predefined XML.
     *
     * Overwrites the default method SoapClient::__doRequest() to make it work
     * with multipart responses or prefixed/suffixed by uuids.
     *
     * @return string The XML Valid SOAP response.
     */
    public function __doRequest($request, $location, $action, $version, $one_way = 0): string
    {
        $result = parent::__doRequest($request, $location, $action, $version, $one_way);
        $headers = $this->__getLastResponseHeaders();

        if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
            preg_match_all(self::SOAP_ENVELOPE_REGEXP, $result, $resultSanitized, PREG_SET_ORDER, 0);
            $result = $resultSanitized[0][0] ?? $result;
        }

        return $result;
    }
}
woprrr
  • 111
  • 2
-2
    class MySoapClient extends SoapClient
    {
        public function __doRequest($request, $location, $action, $version, $one_way = 0)
        {
            $response = parent::__doRequest($request, $location, $action, $version, $one_way);
            // strip away everything but the xml.
            $response = preg_replace('#^.*(<\?xml.*>)[^>]*$#s', '$1', $response);
            return $response;
        }
    }
technophyle
  • 7,972
  • 6
  • 29
  • 50
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 03 '23 at 01:30
  • Answers in English only please. – General Grievance Jun 06 '23 at 03:22