Is there such a beastie? The simple SOAP client that ships with PHP does not understand multi-part messages. Thanks in advance.
7 Answers
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
SoapClientclass and overwrite theSoapClient::__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.
- 82,642
 - 24
 - 155
 - 189
 
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.
- 2,192
 - 1
 - 19
 - 29
 
- 
                    Works like a charm, and saved me a lot of time. Thanks! – NorthBridge Jan 23 '19 at 14:43
 
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;
    }
}
- 10,525
 - 13
 - 49
 - 44
 
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);
?>
- 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
 
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;
}
- 10,839
 - 4
 - 25
 - 26
 
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;
    }
}
- 111
 - 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;
        }
    }
- 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
 -