PHPのSimpleXMLオブジェクトに対してエラー処理をしようとしたところ、改めてこのオブジェクトの仕組みが分からなくなり、理解するのにだいぶかかった。「あれっ」となるところがあり一筋縄ではいかなかったので、まとめておきます。
※ 以下のコードの実行環境は、PHP 7.4 です
存在しないプロパティを参照すると「空のSimpleXMLオブジェクト」が返る
次の処理は、XMLから、ユーザ名を取得し配列にしています。
<?php
$xmlstr = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<code>001</code>
<name>Yamada</name>
</user>
<user>
<code>002</code>
<name>Tanaka</name>
</user>
<user>
<code>003</code>
<name>Sato</name>
</user>
</users>
XML;
$xml = new SimpleXMLElement($xmlstr);
$user_names = [];
foreach ($xml->user as $user) {
$user_names[] = (string)$user->name;
}
print_r($user_names); // Array ( [0] => Yamada [1] => Tanaka [2] => Sato )
SimpleXMLオブジェクトの存在しないプロパティ(XMLにタグがない)を参照してみると
空のSimpleXMLオブジェクト
が戻り値になることが分かります。
通常のオブジェクトは、Noticeエラーが発生しますが、SimpleXMLオブジェクトではそれが出ない。
print_r($xml->hoge); // SimpleXMLElement Object ( )
$std = new stdClass();
print_r($std->hoge); // Notice: Undefined property: stdClass::$hoge
存在しないプロパティを検証した時は、次のようになりました。
isset | !empty | !is_null | |
---|---|---|---|
SimpleXMLElement | false | false | true |
stdClass | false | false | false |
※ stdClass の、!is_null では、「Notice: Undefined property: stdClass::$hoge」とエラーが出ます |
isset
は、プロパティを直接見に行き、存在しない(宣言されていない)ので、false
になる。
!empty
も、isset
と同じく、プロパティを直接見に行き、空と見なすということだと考えられます。
!is_null
は、is_null()
を実行するとき、存在しないプロパティへの参照が発生し、
空のSimpleXMLオブジェクト
が返されるため、null
ではないと判断されるようです。
stdClass
は、存在しない値で、null
が返るため、逆の結果になります。
関数は参照が発生するので、発生しない言語構造のisset
、!empty
とは動きが
違ってくるということでしょう。
##「空のSimpleXMLオブジェクト」は、オブジェクトだけど空
SimpleXMLオブジェクト と stdClass の比較では、空の時の検証結果が変わってきます。
空のSimpleXMLオブジェクト
は、空とみなされるが、
空のstdClassオブジェクト
は、空とみなされない。
isset | !empty | !is_null | |
---|---|---|---|
空のSimpleXMLオブジェクト | true | false | true |
空のstdClass | true | true | true |
配列と同じように空のSimpleXMLオブジェクト
は、empty
で空とみなされます。
この辺りは、マニュアルにも記載がありました。
empty
は、「booleanへの変換」で結果を判断しており、
false(空)とみなす値に空のSimpleXMLオブジェクト
も入っています。
属性がない空要素から作成された SimpleXML オブジェクト。つまり、子要素も属性もない要素です。
##「空のSimpleXMLオブジェクト」にみえるけどデータがあることがある
以下のようにXML名前空間が要素に付いているという時は、注意が必要です
<?php
$xmlstr = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<hoge:users xmlns:hoge="http://mokkii-hoge.org/2021/">
<hoge:user>
<hoge:code>001</hoge:code>
<hoge:name>Yamada</hoge:name>
</hoge:user>
<hoge:user>
<hoge:code>002</hoge:code>
<hoge:name>Tanaka</hoge:name>
</hoge:user>
<hoge:user>
<hoge:code>003</hoge:code>
<hoge:name>Sato</hoge:name>
</hoge:user>
</hoge:users>
XML;
$xml = new SimpleXMLElement($xmlstr);
print_r($xml); // SimpleXMLElement Object ( )
var_export(isset($xml)); // true
var_export(!empty($xml)); // false
var_export(!is_null($xml)); // true
$user_names = [];
foreach ($xml->children('hoge', true) as $user) {
$user_names[] = (string)$user->name;
}
print_r($user_names); // Array ( [0] => Yamada [1] => Tanaka [2] => Sato )
中身があるはずなのですが、print_r()
で中身を出力してみると、
みかけは 空のSimpleXMLオブジェクト
と同じになります。
別のXML名前空間にデータが存在していても、プリフィクスなしの
デフォルトの名前空間にデータが無いと空と見なすようです。
もちろん、children()
を実行し、名前空間を指定すれば、データを持ってこれます。
SimpleXMLオブジェクト、難しい
SimpleXMLオブジェクトは、かなり独特な動きをするように思いました。
普段のPHPを使っている時の動きとは異なる点があり、
もしかしたら、エラーが出にくいように配慮されているのかもしれないですが、
値の検証などをやりだすとなかなか難しいです。
ある程度、特殊なものとして認識して使うのがいいように思います。