PHPでのxmlパースについて

採用はこちら

PHPでXMLを扱う場面は、今でも意外と多いです。

たとえば、RSS、Sitemap、外部APIのレスポンス、業務システム連携、設定ファイル、フィードデータの取り込みなどでXMLが使われます。

PHPでは主に次の方法でXMLを扱います。

  • SimpleXML
  • DOMDocument
  • XMLReader
  • 必要に応じて XPath
  • 検証用途では XSD / DTD / Relax NG

それぞれ役割が違うので、まずは「どの道具をどの場面で使うか」を押さえるのが大切です。

PHP公式でも、SimpleXMLは簡単なオブジェクト的操作向け、DOMはDOM APIによる文書操作向け、XMLReaderは前進しながら読むプルパーサとして説明されています。

目次

まず結論: どれを使うべきか

ざっくり言うと、使い分けはこうです。

SimpleXML

小〜中規模のXMLを読み取るのに向いています。

短く書けて分かりやすいので、最初に覚えるならこれです。

SimpleXMLは、XMLを通常のプロパティアクセスや反復処理で扱いやすいオブジェクトに変換するための拡張です。

DOMDocument

XMLを厳密に編集・生成・検証したいときに向いています。

PHPのDOM拡張は、XMLやHTML文書に対してDOM API経由で操作するためのものです。

XMLReader

大きなXMLを低メモリで順次処理したいときに向いています。

XMLReaderは「カーソルを前に進めながらノードを読む」XML Pull parserです。

巨大なXMLファイルの取り込みで特に有効です。

SimpleXML

SimpleXMLは、XMLをもっとも手軽に読む方法です。

PHP公式では、SimpleXMLは「通常のプロパティセレクタや配列イテレータで処理できるオブジェクトへXMLを変換する、とてもシンプルで使いやすいツールセット」と説明されています。

向いている用途

  • RSSやSitemapの取得
  • 比較的シンプルなAPIレスポンスの解析
  • 小〜中規模XMLの読み取り
  • まず素早く値を取りたいケース

サンプル

<?php

$xmlString = <<<XML
<bookstore>
    <book id="101">
        <title>PHP入門</title>
        <author>山田太郎</author>
        <price>2800</price>
    </book>
    <book id="102">
        <title>XML実践</title>
        <author>佐藤花子</author>
        <price>3200</price>
    </book>
</bookstore>
XML;

$xml = simplexml_load_string($xmlString);

foreach ($xml->book as $book) {
    echo "ID: " . (string)$book['id'] . PHP_EOL;
    echo "タイトル: " . (string)$book->title . PHP_EOL;
    echo "著者: " . (string)$book->author . PHP_EOL;
    echo "価格: " . (int)$book->price . PHP_EOL;
    echo "------" . PHP_EOL;
}

ポイント

  • 子要素は $xml->book
  • 属性は $book['id']
  • 値を扱うときは (string)(int) にキャストしておくと安全

ここはかなり重要です。

SimpleXMLの値は SimpleXMLElement オブジェクトとして返るので、そのまま保持したり別処理に渡したりすると扱いにくいことがあります。

文字列や数値として使うなら、明示的に型変換する癖をつけたほうが実務では安定します。

PHPマニュアルの利用例や注記でも、この扱いは実務上よく問題になります。

注意点

SimpleXMLは便利ですが、次のようなケースでは急に扱いづらくなります。

  • 名前空間つきXML
  • mixed content
  • 複雑なネスト
  • 厳密なノード編集

つまり、「読むのは簡単、でも複雑になると限界が見えやすい」という理解が正確です。

DOMDocument

DOMDocumentは、XMLを木構造として厳密に扱うための方法です。

PHPのDOM拡張は、DOM APIを通してXMLやHTML文書を操作するためのもので、要素追加・削除・属性変更・整形出力・検証などに向いています。

向いている用途

  • XMLの生成
  • XMLの編集
  • 属性やノードの細かい操作
  • XSDやDTDによる検証
  • mixed contentの扱い

サンプル

<?php

$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;

$bookstore = $dom->createElement('bookstore');
$dom->appendChild($bookstore);

$book = $dom->createElement('book');
$book->setAttribute('id', '103');

$book->appendChild($dom->createElement('title', 'Laravel実践'));
$book->appendChild($dom->createElement('author', '鈴木一郎'));
$book->appendChild($dom->createElement('price', '3500'));

$bookstore->appendChild($book);

echo $dom->saveXML();

補足

DOMはSimpleXMLより冗長ですが、その代わり構造を細かく制御できるのが強みです。

また、DOM拡張はUTF-8 encoding を使うと公式に記載されています。UTF-8以外の文字コードを扱うときは、mb_convert_encoding()iconv() などでの変換を考える必要があります。

XMLReader

XMLReaderは、大きなXMLを順次読み進めるための手段です。

PHP公式では、XMLReaderは「カーソルが文書ストリームを前進しながら各ノードで止まる XML Pull parser」と説明されています。

つまり、文書全体を一気にメモリに展開するのではなく、必要な位置まで進めながら読むイメージです。

向いている用途

  • 巨大XMLファイル
  • 商品データやマスターデータの一括取込
  • 1件ずつDB登録する処理
  • メモリ消費を抑えたい処理

サンプル

<?php

$reader = new XMLReader();
$reader->open('books.xml');

while ($reader->read()) {
    if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'book') {
        $nodeXml = $reader->readOuterXml();
        $book = simplexml_load_string($nodeXml);

        echo (string)$book['id'] . PHP_EOL;
        echo (string)$book->title . PHP_EOL;
    }
}

$reader->close();

この書き方の意味

このパターンは実務でよく使われます。

  • 全体の走査は XMLReader
  • 個別ノードの読みやすい処理は SimpleXML

という分担です。

ただし、ここで注意があります。

XMLReader::readOuterXml() は便利ですが、公式にはPHPが libxml 20620 以降でコンパイルされている場合に使えるとあります。

現在の一般的な環境では問題になりにくいですが、前提条件として知っておくと正確です。

また、この方法は「対象ノードを文字列として取り出して再パースする」ので、全読み込みより軽い一方で、その再パース分のコストはあります。

超大規模処理では expand() などを含めて実装方針を考える価値があります。

XPath

XMLの構造が少し複雑になると、XPathが非常に便利です。

SimpleXMLには xpath() があり、条件付き検索や深い階層の検索に使えます。

SimpleXMLのメソッド一覧にも xpath() があり、XPathクエリを実行できることが示されています。

サンプル

<?php

$xmlString = <<<XML
<bookstore>
    <book id="101">
        <title>PHP入門</title>
        <price>2800</price>
    </book>
    <book id="102">
        <title>XML実践</title>
        <price>3200</price>
    </book>
</bookstore>
XML;

$xml = simplexml_load_string($xmlString);

$result = $xml->xpath('//book[price > 3000]');

foreach ($result as $book) {
    echo (string)$book->title . PHP_EOL;
}

XPathが向いているケース

  • 深い階層から条件に合うノードだけ取りたい
  • 属性条件で絞りたい
  • 同じ構造を横断的に取得したい

名前空間つきXML

ここは、PHPでXMLを扱うときにかなり重要な山場です。

SOAP、Atom、Sitemap、Google系のXML、業務連携XMLでは、名前空間がよく出ます。

このとき、見た目に ns:item と書かれていても、単純に $xml->item ではうまく取れないことがあります。

SimpleXMLには

  • children()
  • attributes()
  • getNamespaces()
  • registerXPathNamespace()

といった仕組みがあります。特に registerXPathNamespace() は、XPathで名前空間を扱うときに重要です。

PHP公式でも、このメソッドは「次のXPathクエリのために prefix / namespace の文脈を作る」と説明されています。

XML提供側がプレフィックス名を変えても、URIに対してこちらでプレフィックスを割り当てて検索できます。

サンプル

<?php

$xmlString = <<<XML
<root xmlns:ns="http://example.com/ns">
    <ns:item>
        <ns:name>商品A</ns:name>
    </ns:item>
</root>
XML;

$xml = simplexml_load_string($xmlString);

$xml->registerXPathNamespace('x', 'http://example.com/ns');
$result = $xml->xpath('//x:item/x:name');

foreach ($result as $name) {
    echo (string)$name . PHP_EOL;
}

重要ポイント

  • 名前空間付きXMLでは、プレフィックス名そのものよりURIが重要
  • XPathで名前空間を使うなら、registerXPathNamespace() を使う
  • これをしないと、クエリが正しく見えても結果が空になることがある

これは実務で非常によくあるハマりどころです。

エラーハンドリング

XMLは、タグの閉じ忘れや不正な構造があると簡単にパースエラーになります。

そのため、エラー処理は必須です。

サンプル

<?php

libxml_use_internal_errors(true);

$xml = simplexml_load_string('<root><item></root>');

if ($xml === false) {
    foreach (libxml_get_errors() as $error) {
        echo trim($error->message) . PHP_EOL;
    }
    libxml_clear_errors();
}

ポイント

  • libxml_use_internal_errors(true) でエラーを内部保持
  • パース失敗時は false
  • libxml_get_errors() で内容取得
  • 最後に libxml_clear_errors() でクリア

この流れは、SimpleXMLでもDOMDocumentでもよく使います。

セキュリティ: XXEの注意点

ここは昔の情報と今の情報が混ざりやすい部分なので、正確に整理しておきます。

以前は、XMLの外部エンティティを悪用する XXE への対策として libxml_disable_entity_loader() がよく使われていました。

しかし、PHP 8.0 の移行情報では、この関数はdeprecatedです。理由は、libxml 2.9.0 が必須になり、外部エンティティ読み込みはデフォルトで無効になっているためです。

PHP公式は、通常のXXE対策としてこの関数は不要であり、例外的に LIBXML_NOENT を使うコードではまだ注意が必要だと説明しています。

つまり今の実務感覚では

  • PHP 8系では、昔ほど libxml_disable_entity_loader() を意識しなくてよい
  • ただし、危険なオプションや外部エンティティ展開を伴う設計は避ける
  • 特に LIBXML_NOENT を軽く使わない

この理解が、現行PHPではかなり正確です。

CDATA

CDATAは、タグとして解釈されたくない文字列をそのまま保持したいときに使われます。

<description><![CDATA[<strong>重要</strong>な説明です]]></description>

SimpleXMLでCDATAを扱うなら、LIBXML_NOCDATA を使うと通常のテキストとして取りやすくなります。

<?php

$xml = simplexml_load_string(
    '<root><description><![CDATA[<strong>重要</strong>な説明です]]></description></root>',
    'SimpleXMLElement',
    LIBXML_NOCDATA
);

echo (string)$xml->description . PHP_EOL;

mixed content

XMLでは、テキストと子要素が混在することがあります。

<p>これは <b>重要</b> です。</p>

こういう構造は、SimpleXMLだと直感的に扱いにくいことがあります。

テキストノードと要素ノードが混ざるためです。

この種のケースは、DOMDocumentのほうが向いていることが多いです。

DOMはノード単位で扱えるので、文字列と要素が混在した構造を追いやすいからです。

文字コード

文字化けは、XML処理の実務でよく起きます。

特に次のようなケースで問題になりやすいです。

  • XML宣言の encoding と実データの文字コードが違う
  • Shift_JIS系のデータが混ざる
  • 外部システムが不正なXMLを返してくる
  • BOM付きデータの影響が出る

DOM拡張はUTF-8を使うので、実務ではUTF-8に統一する方針がかなり安定します。

必要に応じて mb_convert_encoding()iconv() を使って前処理するのが定番です。

XMLを配列に変換したいとき

よくある簡易テクニックとして、SimpleXMLをJSON経由で配列化する方法があります。

<?php

$xml = simplexml_load_string('<user><name>田中</name><age>30</age></user>');
$array = json_decode(json_encode($xml), true);

print_r($array);

これは手軽ですが、万能ではありません

問題になりやすい点

  • 属性が扱いづらい
  • 名前空間が壊れやすい
  • 単数要素と複数要素で構造がぶれやすい
  • mixed contentに弱い

PHPマニュアルのユーザーノートでも、この簡易変換や、それを補うための再帰変換例が見られます。

つまり、この方法は「使えなくはないが、厳密用途には向かない」という理解が妥当です。

業務用途では、必要な値を明示的に取り出して配列に整形する方が安全です。

XSDでのバリデーション

XMLが「決められた構造を満たしているか」を検証したい場合、XSDが使えます。

PHPの DOMDocument::schemaValidate() は、指定したスキーマファイルに基づいて文書を検証するメソッドで、公式には XML Schema 1.0 のみ対応 と明記されています。

サンプル

<?php

libxml_use_internal_errors(true);

$dom = new DOMDocument();
$dom->load('sample.xml');

if (!$dom->schemaValidate('schema.xsd')) {
    foreach (libxml_get_errors() as $error) {
        echo trim($error->message) . PHP_EOL;
    }
    libxml_clear_errors();
} else {
    echo "XSDバリデーションOK" . PHP_EOL;
}

向いている用途

  • 受発注データ
  • 固定仕様の業務XML
  • 外部連携フォーマットの検証
  • 取り込み前チェック

実務でのおすすめの使い分け

最後に、実務向けに整理するとこうなります。

小さくて単純なXML

SimpleXML

  • RSS
  • Sitemap
  • 小さなAPIレスポンス
  • 設定ファイル

編集・生成・厳密操作

DOMDocument

  • XML生成
  • ノード追加・削除
  • mixed content対応
  • XSD検証

巨大XML

XMLReader

  • 数万件以上のレコード取り込み
  • 商品フィード
  • 大規模マスターデータ
  • ストリーム処理

覚えておくと強い要点

大事なところだけ絞ると、次の理解がかなり実務的です。

  • 簡単に読むなら SimpleXML
  • 厳密に編集・生成するなら DOMDocument
  • 巨大XMLは XMLReader
  • 複雑な検索は XPath
  • 名前空間対応は必須知識
  • PHP 8系では XXE 対策の前提が昔と少し違う
  • 配列化は雑にやると壊れやすい
  • 文字コードは UTF-8前提で考えると安定しやすい

以上、PHPでのxmlパースについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次