In answering a previous question I found the following behaviour which I can't understand. The following code shows the issue...
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
$data = <<< XML
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value></Value>
</Data>
</Base>
XML;
$xml = simplexml_load_string($data);
foreach ( $xml->Data->Value as $value ) {
$value = 1;
}
echo $xml->asXML().PHP_EOL;
foreach ( $xml->Data as $value ) {
$value->Value = 1;
}
echo $xml->asXML().PHP_EOL;
I would expect the output at each point to be the same, but the output is...
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value/>
</Data>
</Base>
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value>1</Value>
</Data>
</Base>
So this seems to indicate that the first loop which directly accesses the <Value> element, doesn't set the value and yet the second loop which indirectly accesses it works OK.
What is the difference?
The difference is nothing to do with the loops, or with references, but with what exactly = means in each case.
The first version can be simplified to this:
$value = $xml->Data->Value;
$value = 1;
This is a straight-forward assignment to a variable, first of one value, and then of another. There's no interaction between the old value and the new one, so $xml is not changed.
The second case can be written like this:
$data = $xml->Data;
$data->Value = 1;
// Or just $xml->Data->Value = 1;
Here, we are assigning not to a normal variable, but to an object property, and the trick is that the object can intercept that assignment, and do something special with it. In this case, it triggers SimpleXML to send the value to the libxml representation of the XML document in memory. It is as though you had run a method call like $data->setValueOfChild('Value', 1);.
Note that if we instead wrote this:
$value =& $xml->Data->Value;
$value = 1;
Now the first assignment sets $value to be a reference, and the second assigns 1 to that reference. This is enough to write the value to an actual object property, but does not trigger the interception SimpleXML needs.
However, there is one additional trick we can use in this particular case: as well as intercepting property access, the SimpleXMLElement class intercepts array access so that you can write $foo->NameThatOccursMoreThanOnce[3] and $some_element['Attribute']. So it turns out we can write this:
$value = $xml->Data->Value;
$value[0] = 1;
Here, $value is a SimpleXMLElement object, which can intercept the $value[0] = 1 as something like $value->setValueOfItem(0, 1).
In this case, the object holds the collection of all elements called <Value> from inside the <Data> element; but conveniently, even if the object has already been narrowed down to one item, [0] just refers back to the same element, so this works too:
$value = $xml->Data->Value[0];
$value[0] = 1;
Finally, a quick note that your own objects can implement this magic behaviour too! The property access can be implemented using the __get, __set, and __unset magic methods, and the array access can be implemented using the ArrayAccess interface.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With