Gundark Gundark - 4 months ago 81
PHP Question

Why php simplexml allow addChild in one of these examples but not the other?

I wonder if someone can explain in clear terms why this bit of code doesn't work:

<?php
$base = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
'<ns:RootNode xmlns:ns="http://www.bogus.com/bogus/">' .
'<ns:RequestHeader/>' .
'<ns:ContractInfo/>' .
'</ns:RootNode>' . "\n";
$xml = simplexml_load_string($base);
$header = $xml->RequestHeader;

echo get_class($header) . "\n";

$header->addChild('SourceID', '123456');
echo $xml->asXML() . "\n\n";


... resulting in this output:

SimpleXMLElement
PHP Warning: SimpleXMLElement::addChild(): Cannot add child. Parent is not
a permanent member of the XML tree in
C:\yadayada\test.php on line 12

Warning: SimpleXMLElement::addChild(): Cannot add child. Parent is not a permanent member of the XML tree in
C:\yadayada\test.php on line 12
<?xml version="1.0" encoding="UTF-8"?>
<ns:RootNode xmlns:ns="http://www.bogus.com/bogus/"><ns:RequestHeader/><ns:ContractInfo/></ns:RootNode>


... but this bit of code does:

$base = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
'<ns:RootNode xmlns:ns="http://www.bogus.com/bogus/">' .
'<ns:ContractInfo/>' .
'</ns:RootNode>' . "\n";
$xml = simplexml_load_string($base);
$header = $xml->addChild('RequestHeader');

echo get_class($header) . "\n";

$header->addChild('SourceID', '123456');
echo $xml->asXML() . "\n\n";


... resulting in this output:

SimpleXMLElement
<?xml version="1.0" encoding="UTF-8"?>
<ns:RootNode xmlns:ns="http://www.bogus.com/bogus/"><ns:ContractInfo/><ns:RequestHeader><ns:SourceID>123456</ns:SourceID></ns:RequestHeader></ns:RootNode>


Basically the only difference I can see is that in the second case I'm adding nodes to the root element, and in the first example I'm adding them to a child element.

I've read through the relevant SimpleXML documentation on PHP.net and it uses examples more like the second one, but I didn't see anything that explicitly prohibits the first method. I also found a number of SO articles talking about similar issues, but none that really explain why it should be that I can't directly add a child node to an existing node using SimpleXML.

What I find strange is that in both cases, the objects the code is trying to modify are the same type, SimpleXMLElement, but that they clearly don't share the same properties, since one allows the addChild and the other does not.

In this case, I've already found a way to make my code work, but I would really like to understand why it works the way it does.




Edit:

Thanks to BeetleJuice for pointing out that the namespaces were not being handled automatically by PHPs SimpleXML processor. A slight modification to the code, suggested by Kevin Yank, makes it work properly. The line

$header = $xml->RequestHeader;


becomes

$header = $xml->children('http://www.bogus.com/bogus/')->RequestHeader;


Problem solved.

Answer

In your first instance, you're thinking that $xml->RequestHeader refers to the <ns:RequestHeader/> node in your source, but it doesn't. You could have used any fake name with no change in the output of $xml->asXML(); So you get a warning when you try to add a child because $header is not actually a member of your $xml tree.

In the second instance, you're adding the child directly to the $xml tree that was parsed, so you get no warning:

$header = $xml->addChild('RequestHeader');

It seems that the SimpleXML -> operator is not designed to sort out your ns: flags. Your first code block works if you remove them from $base

$base = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
    '<RootNode xmlns="http://www.bogus.com/bogus/">' .
    '<RequestHeader/>' .
    '<ContractInfo/>' .
    '</RootNode>' . "\n";
$xml = simplexml_load_string($base);
$header = $xml->RequestHeader;

$header->addChild('SourceID', '123456');
echo $xml->asXML() . "\n\n";

This raises no warning