1
0

More than 1 year has passed since last update.

【PHP】SimpleXMLオブジェクトの不思議な挙動

Posted at

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0