Remove a child with a specific attribute, in SimpleXML for PHP

Just unset the node:

$str = <<<STR
<a>
  <b>
    <c>
    </c>
  </b>
</a>
STR;

$xml = simplexml_load_string($str);
unset($xml –> a –> b –> c); // this would remove node c
echo $xml –> asXML(); // xml document string without node c

This code was taken from How to delete / remove nodes in SimpleXML.


I believe Stefan's answer is right on. If you want to remove only one node (rather than all matching nodes), here is another example:

//Load XML from file (or it could come from a POST, etc.)
$xml = simplexml_load_file('fileName.xml');

//Use XPath to find target node for removal
$target = $xml->xpath("//seg[@id=$uniqueIdToDelete]");

//If target does not exist (already deleted by someone/thing else), halt
if(!$target)
return; //Returns null

//Import simpleXml reference into Dom & do removal (removal occurs in simpleXML object)
$domRef = dom_import_simplexml($target[0]); //Select position 0 in XPath array
$domRef->parentNode->removeChild($domRef);

//Format XML to save indented tree rather than one line and save
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
$dom->save('fileName.xml');

Note that sections Load XML... (first) and Format XML... (last) could be replaced with different code depending on where your XML data comes from and what you want to do with the output; it is the sections in between that find a node and remove it.

In addition, the if statement is only there to ensure that the target node exists before trying to move it. You could choose different ways to handle or ignore this case.


Contrary to popular belief in the existing answers, each Simplexml element node can be removed from the document just by itself and unset(). The point in case is just that you need to understand how SimpleXML actually works.

First locate the element you want to remove:

list($element) = $doc->xpath('/*/seg[@id="A12"]');

Then remove the element represented in $element you unset its self-reference:

unset($element[0]);

This works because the first element of any element is the element itself in Simplexml (self-reference). This has to do with its magic nature, numeric indices are representing the elements in any list (e.g. parent->children), and even the single child is such a list.

Non-numeric string indices represent attributes (in array-access) or child-element(s) (in property-access).

Therefore numeric indecies in property-access like:

unset($element->{0});

work as well.

Naturally with that xpath example, it is rather straight forward (in PHP 5.4):

unset($doc->xpath('/*/seg[@id="A12"]')[0][0]);

The full example code (Demo):

<?php
/**
 * Remove a child with a specific attribute, in SimpleXML for PHP
 * @link http://stackoverflow.com/a/16062633/367456
 */

$data=<<<DATA
<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>
DATA;


$doc = new SimpleXMLElement($data);

unset($doc->xpath('seg[@id="A12"]')[0]->{0});

$doc->asXml('php://output');

Output:

<?xml version="1.0"?>
<data>
    <seg id="A1"/>
    <seg id="A5"/>

    <seg id="A29"/>
    <seg id="A30"/>
</data>

While SimpleXML provides a way to remove XML nodes, its modification capabilities are somewhat limited. One other solution is to resort to using the DOM extension. dom_import_simplexml() will help you with converting your SimpleXMLElement into a DOMElement.

Just some example code (tested with PHP 5.2.5):

$data='<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>';
$doc=new SimpleXMLElement($data);
foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12') {
        $dom=dom_import_simplexml($seg);
        $dom->parentNode->removeChild($dom);
    }
}
echo $doc->asXml();

outputs

<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>

By the way: selecting specific nodes is much more simple when you use XPath (SimpleXMLElement->xpath):

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above