65

SOAP XML:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <PaymentNotification xmlns="http://apilistener.envoyservices.com">
      <payment>
        <uniqueReference>ESDEUR11039872</uniqueReference>      
        <epacsReference>74348dc0-cbf0-df11-b725-001ec9e61285</epacsReference>
        <postingDate>2010-11-15T15:19:45</postingDate>
        <bankCurrency>EUR</bankCurrency>
        <bankAmount>1.00</bankAmount>
        <appliedCurrency>EUR</appliedCurrency>
        <appliedAmount>1.00</appliedAmount>
        <countryCode>ES</countryCode>
        <bankInformation>Sean Wood</bankInformation>
  <merchantReference>ESDEUR11039872</merchantReference>
   </payment>
    </PaymentNotification>
  </soap:Body>
</soap:Envelope>

How to get 'payment' element?

I try to parse (PHP)

$xml = simplexml_load_string($soap_response);
$xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
foreach ($xml->xpath('//payment') as $item)
{
    print_r($item);
}

Result is empty :( Any ideas how to parse it correct?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Anton
  • 651
  • 1
  • 5
  • 4
  • welcome to SO. next time, click the format button at the top of the textarea so the code is easier to read. – bcosca Nov 16 '10 at 13:19
  • @stillstanding: Ooh, apparently I overwrote your edit just as you submitted it :) – BoltClock Nov 16 '10 at 13:21
  • As you are fairly new here, I recommend you to read http://stackoverflow.com/faq#howtoask and then accept my answer, as the most relevant ;-) – Neeme Praks Nov 16 '10 at 14:43

8 Answers8

122

One of the simplest ways to handle namespace prefixes is simply to strip them from the XML response before passing it through to simplexml such as below:

$your_xml_response = '<Your XML here>';
$clean_xml = str_ireplace(['SOAP-ENV:', 'SOAP:'], '', $your_xml_response);
$xml = simplexml_load_string($clean_xml);

This would return the following:

SimpleXMLElement Object
(
    [Body] => SimpleXMLElement Object
        (
            [PaymentNotification] => SimpleXMLElement Object
                (
                    [payment] => SimpleXMLElement Object
                        (
                            [uniqueReference] => ESDEUR11039872
                            [epacsReference] => 74348dc0-cbf0-df11-b725-001ec9e61285
                            [postingDate] => 2010-11-15T15:19:45
                            [bankCurrency] => EUR
                            [bankAmount] => 1.00
                            [appliedCurrency] => EUR
                            [appliedAmount] => 1.00
                            [countryCode] => ES
                            [bankInformation] => Sean Wood
                            [merchantReference] => ESDEUR11039872
                        )

                )

        )

)
Aran
  • 2,429
  • 1
  • 19
  • 19
  • 4
    Namespaces are usually misleading and removing it seems to be the fastest (hack) way to restore proper xml object. thx – Paulo Bueno Dec 17 '14 at 13:09
  • 56
    awesome solution. soap sucks. – John Ballinger Jun 18 '15 at 05:18
  • 4
    In my case I had to add a couple more array values to str_ireplace but in the end this was the only working solution I found. Upvoted. Also agreed with @JohnBallinger soap sucks. – GTCrais Nov 19 '15 at 17:11
  • 1
    Thanks! this save me a lot of time. Just replace namespaces with str_replace and a simple xML object will work. – Skatox Jan 12 '17 at 15:39
  • 1
    Added some values to the str_ireplace works for me :) ['SOAP-ENV:', 'env:', 'SOAP:', 'ns1:'] – ReaperSoon Nov 16 '17 at 10:52
  • this is a cool idea, but i had multiple different namespaces, so had to get flexible: `$xml = preg_replace('@<(/?)([a-z]+):@iu', '<$1', $xml);` – ᴍᴇʜᴏᴠ Jan 10 '19 at 16:51
  • This is very good, how do i reach 'payment'? iv'e tried ````$var->Body->PaymentNotification->payment```` and i have tried ````$var['Body']['Payment'].....```` – Alexis Garcia Sep 30 '19 at 11:38
  • 1
    @AlexisGarcia based on the above code, you'd need to use `$xml->Body->PaymentNotification->payment` – Aran Oct 01 '19 at 14:59
  • I need parse the header tag as we in my xml there is body tag and this soloution working perfect can you help me how to parse header tag as well this is tag and below is my header tag xml – Umer Singhera Jan 22 '20 at 20:00
  • The most useful solution in all of the internet, thanks dude #soapsucks – Chrishow Oct 22 '21 at 16:40
48

PHP version > 5.0 has a nice SoapClient integrated. Which doesn't require to parse response xml. Here's a quick example

$client = new SoapClient("http://path.to/wsdl?WSDL");
$res = $client->SoapFunction(array('param1'=>'value','param2'=>'value'));
echo $res->PaymentNotification->payment;
Ivan
  • 3,567
  • 17
  • 25
  • 2
    What is SoapFunction here? – Edward Jun 11 '13 at 12:08
  • 1
    @Edward it's any function(operation) defined in the SOAP service's WSDL you want to access – Ivan Jun 11 '13 at 15:42
  • @Ivan So it's one of those services listed in the XML on the asmx server? By the way, you think you could take a look at my question related to this? http://stackoverflow.com/questions/17042984/php-what-soap-method-is-best-for-request-to-asmx-server – Edward Jun 11 '13 at 15:52
  • @Edvard yes. SOAP server will define what kind of operations it can perform and what parameters are needed for each operation. so in the example above. "Soap Function" is a name of an operation as defined by Soap server. Parameters go in an array – Ivan Jun 12 '13 at 11:43
26

In your code you are querying for the payment element in default namespace, but in the XML response it is declared as in http://apilistener.envoyservices.com namespace.

So, you are missing a namespace declaration:

$xml->registerXPathNamespace('envoy', 'http://apilistener.envoyservices.com');

Now you can use the envoy namespace prefix in your xpath query:

xpath('//envoy:payment')

The full code would be:

$xml = simplexml_load_string($soap_response);
$xml->registerXPathNamespace('envoy', 'http://apilistener.envoyservices.com');
foreach ($xml->xpath('//envoy:payment') as $item)
{
    print_r($item);
}

Note: I removed the soap namespace declaration as you do not seem to be using it (it is only useful if you would use the namespace prefix in you xpath queries).

Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
  • But why is the namespace ‘envoy’? Why not ‘soap’? – TomTerrific May 16 '19 at 02:00
  • XML document in the question has elements with namespace URI "http://apilistener.envoyservices.com" and the question is about how to query for elements in that namespace. You do not have to call the prefix "envoy" (can be whatever unique in the scope of XPath query) but "envoy" seemed like a logical name, based on the namespace URI. I would not call it "soap" as that should be reserved for "http://schemas.xmlsoap.org/soap/envelope/" namespace. – Neeme Praks May 17 '19 at 06:07
17
$xml = '<?xml version="1.0" encoding="utf-8"?>
                <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
                  <soap:Body>
                    <PaymentNotification xmlns="http://apilistener.envoyservices.com">
                      <payment>
                        <uniqueReference>ESDEUR11039872</uniqueReference>
                        <epacsReference>74348dc0-cbf0-df11-b725-001ec9e61285</epacsReference>
                        <postingDate>2010-11-15T15:19:45</postingDate>
                        <bankCurrency>EUR</bankCurrency>
                        <bankAmount>1.00</bankAmount>
                        <appliedCurrency>EUR</appliedCurrency>
                        <appliedAmount>1.00</appliedAmount>
                        <countryCode>ES</countryCode>
                        <bankInformation>Sean Wood</bankInformation>
                  <merchantReference>ESDEUR11039872</merchantReference>
                   </payment>
                    </PaymentNotification>
                  </soap:Body>
                </soap:Envelope>';
        $doc = new DOMDocument();
        $doc->loadXML($xml);
        echo $doc->getElementsByTagName('postingDate')->item(0)->nodeValue;
        die;

Result is:

2010-11-15T15:19:45
Nam Nguyen
  • 2,438
  • 21
  • 18
4

First, we need to filter the XML so as to parse that into an object

$response = strtr($xml_string, ['</soap:' => '</', '<soap:' => '<']);
$output = json_decode(json_encode(simplexml_load_string($response)));
var_dump($output->Body->PaymentNotification->payment);
0

This is also quite nice if you subsequently need to resolve any objects into arrays: $array = json_decode(json_encode($responseXmlObject), true);

omarjebari
  • 4,861
  • 3
  • 34
  • 32
-1

First, we need to filter the XML so as to parse that change objects become array

//catch xml
$xmlElement = file_get_contents ('php://input');
//change become array
$Data = (array)simplexml_load_string($xmlElement);
//and see
print_r($Data);
-2

why don't u try using an absolute xPath

//soap:Envelope[1]/soap:Body[1]/PaymentNotification[1]/payment

or since u know that it is a payment and payment doesn't have any attributes just select directly from payment

//soap:Envelope[1]/soap:Body[1]/PaymentNotification[1]/payment/*
almightyBob
  • 152
  • 4
  • 12
  • 1
    foreach ($xml->xpath('//soap:Envelope[1]/soap:Body[1]/PaymentNotification[1]/payment/*') as $item) { print_r($item); } also empty ( – Anton Nov 16 '10 at 13:39
  • try giving your second namespace an actual value. and define that then. – almightyBob Nov 16 '10 at 13:51
  • could you give example please – Anton Nov 16 '10 at 13:58
  • you can see that approach in my response ;-) – Neeme Praks Nov 16 '10 at 13:59
  • result is the same -> foreach ($xml->xpath('//soap:Envelope[1]/soap:Body[1]/PaymentNotification[1]/payment/*') as $item){print_r($item);} – Anton Nov 16 '10 at 14:03
  • I don't know php but try it without the for-each – almightyBob Nov 16 '10 at 14:11
  • as I stated in my response, you first have to register the custom namespace: $xml->registerXPathNamespace('envoy', 'http://apilistener.envoyservices.com'); and then you can use that namespace to look up the element: $xml->xpath('//envoy:payment'); Or you can use the full xpath as suggested by Bob: //soap:Envelope[1]/soap:Body[1]/envoy:PaymentNotification[1]/envoy:payment – Neeme Praks Nov 16 '10 at 14:16
  • Neeme Praks,almightyBob - Thanks a lot! – Anton Nov 16 '10 at 14:26