使えなすぎ
◆ Simple は「簡単に使える」じゃなくて「機能がなさすぎでまともに使えない」ってことだと思う
◆ データの取得に関してなら十分だけど XML の編集だと DOMDocument のほうが遥かに簡単
◆ JavaScript で DOM を触った経験あるなら特に DOMDocument がオススメです 

SimpleXMLElement が使えなすぎです
初心者は使うべきじゃないと思います

SimpleXMLElement

$x = simplexml_load_string('
<x>
    <a aa="1" bb="zz">
        <b cc="4">test</b>
        <b cc="5">test2</b>
        <b cc="6">test3</b>
        <c><d></d></c>
    </a>
    <e gg="kk">text</e>
</x>
');

printHR('x');
var_dump($x);

// 見やすく表示するだけ
function printHR($text){
$line_chars = 50;
echo $text . ' ' . str_repeat('#', $line_chars - strlen($text) - 1) . PHP_EOL;
}
object(SimpleXMLElement)#1 (2) {
  ["a"]=>
  object(SimpleXMLElement)#2 (3) {
    ["@attributes"]=>
    array(2) {
      ["aa"]=>
      string(1) "1"
      ["bb"]=>
      string(2) "zz"
    }
    ["b"]=>
    array(3) {
      [0]=>
      string(4) "test"
      [1]=>
      string(5) "test2"
      [2]=>
      string(5) "test3"
    }
    ["c"]=>
    object(SimpleXMLElement)#3 (1) {
      ["d"]=>
      object(SimpleXMLElement)#4 (0) {
      }
    }
  }
  ["e"]=>
  string(4) "text"
}

e を取り出してみる
printHR("x['e']");
var_dump($x['e']);

printHR('x->e');
var_dump($x->e);
x['e'] ###########################################
NULL
x->e #############################################
object(SimpleXMLElement)#2 (2) {
  ["@attributes"]=>
  array(1) {
    ["gg"]=>
    string(2) "kk"
  }
  [0]=>
  string(4) "text"
}

配列っぽい var_dump 表示だけど SimpleXMLElement のオブジェクトなので -> で取得

printHR('x->a');
var_dump($x->a);

printHR('x->a->b');
var_dump($x->a->b);

printHR('x->e');
var_dump($x->e);
x->a #############################################
object(SimpleXMLElement)#2 (3) {
  ["@attributes"]=>
  array(2) {
    ["aa"]=>
    string(1) "1"
    ["bb"]=>
    string(2) "zz"
  }
  ["b"]=>
  array(3) {
    [0]=>
    string(4) "test"
    [1]=>
    string(5) "test2"
    [2]=>
    string(5) "test3"
  }
  ["c"]=>
  object(SimpleXMLElement)#4 (1) {
    ["d"]=>
    object(SimpleXMLElement)#5 (0) {
    }
  }
}
x->a->b ##########################################
object(SimpleXMLElement)#4 (4) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "4"
  }
  [0]=>
  string(4) "test"
  [1]=>
  string(5) "test2"
  [2]=>
  string(5) "test3"
}
x->e #############################################
object(SimpleXMLElement)#4 (2) {
  ["@attributes"]=>
  array(1) {
    ["gg"]=>
    string(2) "kk"
  }
  [0]=>
  string(4) "text"
}

属性が @attributes子要素はタグ名になってる

a をみると b が配列で要素数 3 になっているのに
a->b で b をとりだしたのだとオブジェクト 配列じゃない
var_dump の表示は当てにしたらダメ

-> でプロパティ取り出しをしたタグがひとつだけだと 子要素のタグ名がプロパティにあるけど text の場合はタグ名じゃなく添字 0 なよう

それから タグが一つじゃない b をみると添字の 0 から順に各 b タグが入っているみたい
タグがひとつか複数かで変わるのも扱いづらい


printHR('x->e->{0}');
var_dump($x->e->{0});

printHR('x->e[0]');
var_dump($x->e[0]);
x->e->{0} ########################################
object(SimpleXMLElement)#4 (0) {
}
x->e[0] ##########################################
object(SimpleXMLElement)#3 (2) {
  ["@attributes"]=>
  array(1) {
    ["gg"]=>
    string(2) "kk"
  }
  [0]=>
  string(4) "text"
}

->{0} は使えなくてここは [0] でアクセス
var_dump からアクセス方法が判別できない
やっぱり var_dump はつk……

だけど結果は x->e と一緒の表示
[0] では中身は取り出せないみたい



attributes にアクセスしようとすると
printHR('x->a->{"@attributes"}');
var_dump($x->a->{"@attributes"});

printHR('x->a["@attributes"]');
var_dump($x->a["@attributes"]);
x->a->{"@attributes"} ############################
object(SimpleXMLElement)#4 (0) {
}
x->a["@attributes"] ##############################
NULL

配列でもオブジェクトでもアクセスできない

そんな感じで var_dump は全然あてにならない

例えばこんな場合
x に a タグとテキストがあります
$x2 = simplexml_load_string('
<x>
    <a>test</a>
    sample
</x>
');

printHR('x2');
var_dump($x2);

printHR('x2->asXML()');
var_dump($x2->asXML());
object(SimpleXMLElement)#3 (1) {
  ["a"]=>
  string(4) "test"
}
string(58) "<?xml version="1.0"?>
<x>
    <a>test</a>
    sample
</x>

var_dump では sample のテキストは見えないですが asXML で XML 化するとちゃんと保持はされています



x->a->b みたいな同じタグが複数あるときに foreach で回すと
printHR("foreach x->a->b");
foreach($x->a->b as $key => $val){
printHR("key");
var_dump($key);
printHR("val");
var_dump($val);
}
foreach x->a->b ##################################
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "4"
  }
  [0]=>
  string(4) "test"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#5 (2) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "5"
  }
  [0]=>
  string(5) "test2"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "6"
  }
  [0]=>
  string(5) "test3"
}

全部 key が一緒!!
あとから一意にアクセスするためのキーとして使えません



text タグが欲しい時
[0] を繰り返しても自分自身を参照しているようで取得できません
printHR("x->e[0][0][0][0][0][0]");
var_dump($x->e[0][0][0][0][0][0]);
x->e[0][0][0][0][0][0] ###########################
object(SimpleXMLElement)#2 (2) {
  ["@attributes"]=>
  array(1) {
    ["gg"]=>
    string(2) "kk"
  }
  [0]=>
  string(4) "text"
}

xpath が使えるようなので使ってみる
printHR("x->xpath('/x/e/text()')");
var_dump($x->xpath('/x/e/text()'));
x->xpath('/x/e/text()') ##########################
array(1) {
  [0]=>
  object(SimpleXMLElement)#2 (2) {
    ["@attributes"]=>
    array(1) {
      ["gg"]=>
      string(2) "kk"
    }
    [0]=>
    string(4) "text"
  }
}

取得できてるのは同じで text 要素じゃないようです

SimpleXMLElement は Element と言ってしまっているので TextNode は対象外?
一応文字列キャストで文字列取得はできます
printHR("(string)x->e");
var_dump((string)$x->e);
(string)x->e #####################################
string(4) "text"

ただし HTML の innerHTML や textContent みたいな動きではなく子要素が TextNode でないと空文字になります
それでも改行は保持されてるようです
printHR("(string)x->a");
var_dump((string)$x->a);
(string)x->a #####################################
string(41) "




    "



リファレンスによると children というメソッドがあるみたい
使っては見たけど
printHR("x");
var_dump($x);

printHR("x->children()");
var_dump($x->children());
x ################################################
object(SimpleXMLElement)#1 (2) {
  ["a"]=>
  object(SimpleXMLElement)#2 (3) {
    ["@attributes"]=>
    array(2) {
      ["aa"]=>
      string(1) "1"
      ["bb"]=>
      string(2) "zz"
    }
    ["b"]=>
    array(3) {
      [0]=>
      string(4) "test"
      [1]=>
      string(5) "test2"
      [2]=>
      string(5) "test3"
    }
    ["c"]=>
    object(SimpleXMLElement)#3 (1) {
      ["d"]=>
      object(SimpleXMLElement)#4 (0) {
      }
    }
  }
  ["e"]=>
  string(4) "text"
}
x->children() ####################################
object(SimpleXMLElement)#2 (2) {
  ["a"]=>
  object(SimpleXMLElement)#4 (3) {
    ["@attributes"]=>
    array(2) {
      ["aa"]=>
      string(1) "1"
      ["bb"]=>
      string(2) "zz"
    }
    ["b"]=>
    array(3) {
      [0]=>
      string(4) "test"
      [1]=>
      string(5) "test2"
      [2]=>
      string(5) "test3"
    }
    ["c"]=>
    object(SimpleXMLElement)#5 (1) {
      ["d"]=>
      object(SimpleXMLElement)#6 (0) {
      }
    }
  }
  ["e"]=>
  string(4) "text"
}

何が違うんだろう
SimpleXMLElement だと最初からプロパティアクセスで子要素が取れるので違いがわかりません


また attributes メソッドもありました
こっちは @attributes で直接属性を取れないので重要です
printHR("x->a->attributes()");
var_dump($x->a->attributes());
x->a->attributes() ###############################
object(SimpleXMLElement)#4 (1) {
  ["@attributes"]=>
  array(2) {
    ["aa"]=>
    string(1) "1"
    ["bb"]=>
    string(2) "zz"
  }
}

属性だけど SimpleXMLElement
printHR("x->a->attributes()->aa");
var_dump($x->a->attributes()->aa);
x->a->attributes()->aa ###########################
object(SimpleXMLElement)#3 (1) {
  [0]=>
  string(1) "1"
}

プロパティアクセスで属性の指定できますが ここでも文字列型では取得できないです


attributes を使う要素が空の場合は var_dump は一緒に見えます
foreach やプロパティアクセスしないと違いがわからないです
$x3 = simplexml_load_string('
<a aa="1" bb="zz"></a>
');

$xattrs = $x3->attributes();

printHR("x3");
var_dump($x3);

printHR("xattrs");
var_dump($xattrs);

printHR("x3->bb");
var_dump($x3->bb);

printHR("xattrs->bb");
var_dump($xattrs->bb);

printHR("foreach x3");
foreach($x3 as $v) var_dump($v);

printHR("foreach xattrs");
foreach($xattrs as $v) var_dump($v);
x3 ###############################################
object(SimpleXMLElement)#2 (1) {
  ["@attributes"]=>
  array(2) {
    ["aa"]=>
    string(1) "1"
    ["bb"]=>
    string(2) "zz"
  }
}
xattrs ###########################################
object(SimpleXMLElement)#3 (1) {
  ["@attributes"]=>
  array(2) {
    ["aa"]=>
    string(1) "1"
    ["bb"]=>
    string(2) "zz"
  }
}
x3->bb ###########################################
object(SimpleXMLElement)#5 (0) {
}
xattrs->bb #######################################
object(SimpleXMLElement)#5 (1) {
  [0]=>
  string(2) "zz"
}
foreach x3 #######################################
foreach xattrs ###################################
object(SimpleXMLElement)#4 (1) {
  [0]=>
  string(1) "1"
}
object(SimpleXMLElement)#6 (1) {
  [0]=>
  string(2) "zz"
}

同じタグが複数ある場合の タグのプロパティ ($x->a->b) の attributes は一番最初のタグのプロパティになっています
$x->a->b[1] のように指定した上で attributes を使う必要があります
printHR("x->a->b->attributes()");
var_dump($x->a->b->attributes());

printHR("x->a->b[1]->attributes()");
var_dump($x->a->b[1]->attributes());
x->a->b->attributes() ############################
object(SimpleXMLElement)#3 (1) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "4"
  }
}
x->a->b[1]->attributes() #########################
object(SimpleXMLElement)#2 (1) {
  ["@attributes"]=>
  array(1) {
    ["cc"]=>
    string(1) "5"
  }
}

タグが複数ある配列のようなものの場合は attributes なしにしてくれたほうがわかりやすいのですけどねー

削除

SimpleXMLElement では追加するメソッドはありますが置き換えや削除がありません
削除や置き換えを大きく行うときは新しく SimpleXMLElement を作ってそっちに必要なものだけ追加していったほうが楽なくらいに面倒です

属性を削除

attributes を取得して それぞれの [0] を unset すればよいのですが unset したタイミングで attributes が更新されるようで次のループで問題が出ます
printHR("remove attributes");
$aattrs = $x->a->attributes();
foreach($aattrs as $v){
unset($v[0]);
}
echo $x->asXML();
remove attributes ################################

Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 18
<?xml version="1.0"?>
<x>
    <a bb="zz">
        <b cc="4">test</b>
        <b cc="5">test2</b>
        <b cc="6">test3</b>
        <c><d/></c>
    </a>
    <e gg="kk">text</e>
</x>
結果も aa は消えましたが bb が残っています
一度 attributes の key を保存してから key を使ったアクセスで消すようにします
printHR("remove attributes");
$aattrs = $x->a->attributes();

$attrs = [];
foreach($aattrs as $k => $v){
$attrs[] = $k;
}
foreach($attrs as $v){
unset($aattrs->$v[0]);
}
echo $x->asXML();
remove attributes ################################
<?xml version="1.0"?>
<x>
    <a>
        <b cc="4">test</b>
        <b cc="5">test2</b>
        <b cc="6">test3</b>
        <c><d/></c>
    </a>
    <e gg="kk">text</e>
</x>

これで a のタグが全部消えました

子要素を削除

innerHTML = "" のような処理をしたいとき
そのタグに null をいれるだけです
printHR("remove inner nodes");
$x->a = null;
echo $x->asXML();
remove inner nodes ###############################
<?xml version="1.0"?>
<x>
    <a aa="1" bb="zz"></a>
    <e gg="kk">text</e>
</x>

xpath で取得したものを foreach する場合は [0] に null を入れます
a と e の中を空にします
printHR("remove inner nodes");
foreach($x->xpath("/x/a|/x/e") as $val){
$val[0] = null;
}

echo $x->asXML();
remove inner nodes ###############################
<?xml version="1.0"?>
<x>
    <a aa="1" bb="zz"></a>
    <e gg="kk"></e>
</x>

要素の削除

要素自身を削除するときは 子要素を消す時とほぼ同じですが null を入れるのではなく unset します
ただし 属性と同じで foreach で unset したらずれのせいでエラーになるので foreach でやる場合は一度 $value を保存しておきます
printHR("remove node");
foreach($x->a->b as $val){
unset($val[0]);
}

echo $x->asXML();
remove node ######################################

Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17
<?xml version="1.0"?>
<x>
    <a aa="1" bb="zz">

        <b cc="5">test2</b>
        <b cc="6">test3</b>
        <c><d/></c>
    </a>
    <e gg="kk">text</e>
</x>
PHP Warning:  main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17

保存しておいたものに unset をかければ消すことができます
printHR("remove node");
$arr = [];
foreach($x->a->b as $val){
$arr[] = $val;
}
foreach($arr as $val){
unset($val[0]);
}

echo $x->asXML();
remove node ######################################
<?xml version="1.0"?>
<x>
    <a aa="1" bb="zz">



        <c><d/></c>
    </a>
    <e gg="kk">text</e>
</x>

ムリなこと

↑では要素を削除したところに空白行が残ってしまっています
SimpleXMLElement では整形するオプションが無いみたいなので消したところは空白になるだけで詰めることはできないです
また 一行になっているものを複数行で見やすい位置で改行とインデントを入れてくれるようなこともできないです

それと <d></d> が <d/> にまとめられているように 空のタグは開始と終了の 2 つでなく一つにされてしまいます
ただし 改行や空白があれば空白文字の TextNode を含んでいるのでそのままです
libxml オプションで LIBXML_NOEMPTYTAG という空タグでも開始と終了の 2 つのタグにするオプションがありますが SimpleXMLElement の asXML (saveXML) では未対応なようです
DOMDocument で使えます

まとめ

シンプルに扱うにしても 削除ができないとか 属性であろうとテキストノードであろうと全部エレメントでひとつのクラスにしてる無理矢理感のせいで扱いづらいとかヒドイ作りだと思います
さすがダメな言語で有名な PHP のデフォルトだけあります

そもそも デバッグ用の var_dump で正しい値が見ることができない辺り使いものにならないと思います
なんのためのデバッグ用関数なのか不明です

これをみると PHP で XML は触れるべきじゃない と思うかもです
というか思うはず

ですが……

DOMDocument がおすすめ

PHP には Simple じゃない XML を扱う DOMDocument というのもあります

こっちは色々メソッドやプロパティがあって複雑そうですがすごく簡単です
JavaScript で DOM 操作をしたことがあれば ほとんど同じ感じで使えます

a の属性表示

$doc = new DOMDocument();
$doc->loadXML('
<x>
    <a aa="1" bb="zz">
        <b cc="4">test</b>
        <b cc="5">test2</b>
        <b cc="6">test3</b>
        <c><d>
        </d></c>
    </a>
    <e gg="kk">text</e>
</x>
');

printHR("a tag attributes");

$a = $doc->getElementsByTagName("a")->item(0);
foreach($a->attributes as $attr){
echo $attr->name . " : " . $attr->value . PHP_EOL;
}
a tag attributes #################################
aa : 1
bb : zz

b の削除

JavaScript と違い NodeList が動的に更新されるので iterator_to_array が必要
printHR("remove b tags");

$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
$doc->formatOutput = true;
var_dump($doc->saveXML());
remove b tags ####################################
string(146) "<?xml version="1.0"?>
<x>
    <a aa="1" bb="zz">



        <c><d>
        </d></c>
    </a>
    <e gg="kk">text</e>
</x>
"

整形

上の例では消したところに空白が残っています
formatOutput を true にしても TextNode で改行などの空白文字があるところはそのまま残ります

なので空白文字を TextNode から除去します
printHR("remove b tags and pretty print");

$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}

removeMargin($doc);

$doc->formatOutput = true;
var_dump($doc->saveXML());

function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text = trim($text_node->data);
if(strlen($text)){
$text_node->data = $text;
}else{
$text_node->parentNode->removeChild($text_node);
}
}
}
remove b tags and pretty print ###################
string(109) "<?xml version="1.0"?>
<x>
  <a aa="1" bb="zz">
    <c>
      <d/>
    </c>
  </a>
  <e gg="kk">text</e>
</x>
"

余白がなくなりましたね
単純に trim してるので 前後の空白を維持したいところでそのまま使えませんが こんな風に空白文字のノードを除去すれば綺麗に整形できます
完全に空文字でも remove しない場合は綺麗に整形されません
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}

removeMargin($doc);

$doc->formatOutput = true;
printHR("remove b tags and pretty print");
var_dump($doc->saveXML());

function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text_node->data = trim($text_node->data);
printHR("text node length");
var_dump(strlen($text_node->data));
}
}
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(4)
text node length #################################
int(0)
remove b tags and pretty print ###################
string(85) "<?xml version="1.0"?>
<x><a aa="1" bb="zz"><c><d></d></c></a><e gg="kk">text</e></x>
"

trim の結果 "text" 以外は空文字になりました
その場合の出力された XML を見ると整形するはずなのに何もされず1行で出力されています
空文字の TextNode を挟むとそこでは整形しても改行挟まないということになります
TextNode で文字列があるわけですから 勝手に整形のための空白を追加するとデータが変わってしまいますからね