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を使っている時の動きとは異なる点があり、
もしかしたら、エラーが出にくいように配慮されているのかもしれないですが、
値の検証などをやりだすとなかなか難しいです。
ある程度、特殊なものとして認識して使うのがいいように思います。