How do you access PHP SimpleXML nodes in a namespace environment?
I want to pragmatically add <param>value</param>
under <request>
. Not just edit it in a string.
$xml = \SimpleXMLElement(
'<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL
. '<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/">' . PHP_EOL
. ' <soap:Body>' . PHP_EOL
. ' <Function xmlns="https://webservices.sveaekonomi.se/webpay">' . PHP_EOL
. ' <request />' . PHP_EOL
. ' </Function>' . PHP_EOL
. ' </soap:Body>' . PHP_EOL
. '</soap:Envelope>'
);
I have attempted the following:
#1
$xml->Envelope->Body->Function->request->addChild('param', 'value');
#2
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->addChild('param', 'value');
#3
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->addChild('param', 'value');
You've made a few common mistakes here, so I'll go through each in turn.
Let's start with the beginning of your first attempt:
$xml->Envelope->Body->...
SimpleXML doesn't have a separate object for the document, only the root element - in this case, Envelope
. So you don't need to say ->Envelope
, you're already there. Without namespaces being involved, you would write:
$xml->Body->...
However, the object doesn't automatically "select" the namespace of that element, so you have to either immediately call ->children()
, or pass the namespace you want to pre-select into the constructor:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->...
// Or
$xml = new SimpleXMLElement($xmlString, 0, false, 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->Body->...
With that in mind, we get to:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->Function->...
This fails because Function
isn't in the same namespace as Body
. The way I like to think of it is that the ->children()
method is a "switch namespace" method, so we switch to the right namespace and carry on:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body
->children('https://webservices.sveaekonomi.se/webpay')->Function->request
->addChild('param', 'value');
This will work!
Your second attempt makes a different mistake:
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->...
The "children" method doesn't let you jump deep into the document - as its name suggests, it gives you the direct children of an element, not the grand-children, great grand-children, and so on. The $xml
variable points to the "Envelope" node, and that doesn't have a child called "request".
There isn't a built-in method for "any descendant of", except by using XPath...
Although seemingly completely different, your third attempt actually fails for the same reason:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->...
The /
operator in XPath similarly means "children of"; there is an operator for "any descendant of", which is //
, so you would have got nearly the right result with this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')->...
That will fail for a slightly more subtle reason: the xpath()
method of SimpleXML always returns an array, not an object, so you have to ask for the first element ([0]
) of that array.
So the working XPath code is this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')[0]->addChild('param', 'value');