Unset child node makes method disappear

2019-09-16 00:23发布

问题:

I parsed an XML with SimpleXML and wanted to truncate it. This is the method:

public function truncate(SimpleXMLElement $xml)
{
    foreach ($xml->children() as $key => $value)
    {
        if ( !empty($key) && isset($xml->$key))
        {
            echo "unset $key";
            unset($xml->$key);
        }
    }

    echo $xml->asXML();
    exit;
}

After the first unset it throws this:

Warning: pat\Persistence\File\Text\XML::truncate(): Node no longer exists in pat/Persistence/File/Text/XML.php on line 36

Well, I didn't want to unset the method.

回答1:

Your are foreach-ing children, but deleting from the parent, if you want to delete empty child try something like this:

foreach ($xml->children() as $child)
{
    if (empty($child)) {
        unset($child[0]);
    }
}
echo $xml->asXML();


回答2:

The error message "Node no longer exists" in simplexml means that you are trying to remove a node that no longer exists.

As you already noticed this does not happen in the first iteration's unset, but in a following iteration. If you now imagine that SimpleXMLElement::children() returns an array with the zero-based index of the children as the key and the child element node as the value, consider the following to better understand:

You have an element with two children. Therefore the children() function gives back an array with two.

$children        =  [ 0 => <child1>, 1 => <child2> ]
                      ^    unset($xml->{0});
                      `-----------------^

$xml->children() =  [ 0 => <child1>, 1 => <child2> ] 

You now remove the first child element, index 0.

And the second child is still there:

$xml->children() =  [ 0 => <child2> ] 

But because you access the children by their number, and the first child has been removed, the second child get's a new index: From previously 1 now 0. However you unset the child at index 1:

$children        =  [ 0 => <child1>, 1 => <child2> ]
                                     ^    unset($xml->{1});
                                     `-----------------^

$xml->children() =  [ 0 => <child2> ] 

Which does not exist any longer:

Node no longer exists

See the error message? So how to solve that? One common way in SimpleXML is to use a simplexml self-reference as outlined by hakre in that question:

unset($value[0]);

As you can see, it does not need to use any key. Another option is that you reverse the children array and unset from the end, not the beginning. That way you don't pull the indexes under your feet:

$children = array_reverse($xml->children());

foreach ($children as $key => $value) {
    ...
        unset($xml->$key);
    }
}

But the self-reference is likely much more easy to use because it also works in other situations where you want to unset something in simplexml, so it's worth you take that well-explained read over there.

The full example at a glance:

public function truncate()
{
    $xml = $this->_xml;

    $children = $xml->children();
    foreach ($children as $index => $child)
    {
        if ($index) {
            unset($child[0]);
        }
    }

    $xml->asXML('php://output');
    exit;
}

I would also say that the exit does not belong into that method, but I left it in so you can better orientate. Instead the method should just do it's job to output the truncated XML and not care additionally about ending the script. Move it out and place it after you call the method instead because that are two different kind of tasks: The one is XML processing and output and the other is to the program flow.