Fatal Error: Unexpected BLOG

主に自分用の備忘録として

【PHP】simplexml_load_stringの挙動がわけわかめ

なのでメモっておく。

参考

PHPのバージョンは 5.3.3です。

simplexml_load_stringは、文字列として渡したXMLを、オブジェクトとして返してくれる便利なやつだけど、時々おかしなことする。

たとえば、↓みたいなXMLを文字列として渡す。

$xml = <<<EOX
<?xml version="1.0" encoding="utf-8"?>
<elements>
  <parent>
    <control>0</control>
    <code>111111</code>
    <name>John Doe</name>
    <children>
      <child>
        <child_code>1111110</child_code>
        <grand_children>
          <grand_child>
            <grand_child_code>001</grand_child_code>
            <age>15</age>
          </grand_child>
          <grand_child>
            <grand_child_code></grand_child_code>
            <age>10</age>
          </grand_child>
        </grand_children>
      </child>
    </children>
  </parent>
</elements>
EOX;

こんな感じで。

    $obj = simplexml_load_string($xml);
    var_dump($obj);

上記を実行すると以下のように出力されます。

object(SimpleXMLElement)#1 (1) {
  ["parent"]=>
  object(SimpleXMLElement)#2 (10) {
    ["control"]=>
    string(1) "0"
    ["code"]=>
    string(6) "111111"
    ["name"]=>
    string(8) "John Doe"
    ["children"]=>
    object(SimpleXMLElement)#4 (1) {
      ["child"]=>
      object(SimpleXMLElement)#5 (5) {
        ["child_code"]=>
        string(7) "1111110"
        ["grand_children"]=>
        object(SimpleXMLElement)#6 (1) {
          ["grand_child"]=>
          array(2) {
            [0]=>
            object(SimpleXMLElement)#7 (2) {
              ["grand_child_code"]=>
              string(3) "001"
              ["age"]=>
              string(2) "15"
            }
            [1]=>
            object(SimpleXMLElement)#8 (2) {
              ["grand_child_code"]=>
              object(SimpleXMLElement)#9 (0) {
              }
              ["age"]=>
              string(2) "10"
            }
          }
        }
      }
    }
  }
}

各要素はデフォルトではSimpleXMLElementクラスのオブジェクトとして返されます。

ところが16行目、'grand_children'プロパティには、grand_childプロパティに要素数2つの配列を持つオブジェクトが格納されてる。話が違う。

ここで、以下のようにしてみます。

    $obj = simplexml_load_string($xml);
    $child = $obj->children->child
    $grand_children = $child->grand_children->grand_child;
    var_dump($grand_children);

上のダンプ結果に従うならば、$grand_childrenにはSimpleXMLElementオブジェクトを二つ格納した配列が返る気がしますが、実際はそうはなりません。

上記の実行結果は以下のようになります。

object(SimpleXMLElement)#9 (2) {
  ["grand_child_code"]=>
  string(3) "001"
  ["age"]=>
  string(2) "15"
}

配列の先頭にあったオブジェクトがダイレクトに格納されてる。

ところが、以下のようなループ処理は期待通りに実行されます。

    foreach($grand_children as $key => $grand_child) {
        var_dump($key);
        var_dump($grand_child);
    }

実行結果は以下の通り。

string(11) "grand_child"
object(SimpleXMLElement)#10 (2) {
  ["grand_child_code"]=>
  string(3) "001"
  ["age"]=>
  string(2) "15"
}
string(11) "grand_child"
object(SimpleXMLElement)#11 (2) {
  ["grand_child_code"]=>
  object(SimpleXMLElement)#10 (0) {
  }
  ["age"]=>
  string(2) "10"
}

ちゃんと配列の各要素に対して処理が実行されます。

ですが、お気づきの通り、$keyに格納される配列のキー値はどちらも"grand_child"です。

元々のXML構造に忠実といえばそうなのかもしれないけど、ちょっと分かりにくい。

 

あとちなみにですが、内容が空の要素はNULLではなく空のオブジェクトとして返されます。

上記の例でいうと、

object(SimpleXMLElement)#11 (2) {
  ["grand_child_code"]=>
  object(SimpleXMLElement)#10 (0) {
  }
  ["age"]=>
  string(2) "10"
}

こいつの"grand_child_code"です。

要素の値の有無を判定するようなときに要注意ですね。文字列にキャストするなりなんなりで回避するとか?