はじめに
前回の記事では、コールバック関数を個別に登録しながらXMLを解析する xml_parse() を解説しました。ハンドラを自分で書いて要素の開始・終了イベントを処理するSAX方式は柔軟である一方、単純に「XMLの内容を配列として取り出したいだけ」という場面では、やや準備が煩雑に感じられることもあります。
そこで便利なのが xml_parse_into_struct() 関数です。この関数は、xml_parse() のようにハンドラを個別に登録する必要がなく、XML文書を1回の呼び出しで「フラットな構造を持つ配列」に変換してくれるという、お手軽さが特徴の関数です。本記事では基本的な使い方から、出力される配列構造の読み解き方、実践的な活用パターンまで詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | xml_parse_into_struct() |
| 所属拡張 | XML Parser拡張(Expatベース、標準で有効) |
| シグネチャ | xml_parse_into_struct(XMLParser $parser, string $data, array &$values, array &$index = null): int |
| 引数1 | $parser — xml_parser_create() で生成したパーサーインスタンス |
| 引数2 | $data — 解析対象のXML文字列 |
| 引数3 | $values — 解析結果が格納される配列(参照渡し) |
| 引数4 | $index — タグ名ごとの$values内インデックスが格納される配列(参照渡し、省略可) |
| 戻り値 | 成功時に 1、失敗時に 0 |
| 対応バージョン | PHP 4以降(PHP 8.0以降は引数の型がXMLParserオブジェクトに変更) |
| 出力形式 | ネストしないフラットな配列(タグ・レベル・タイプなどの情報を持つ) |
変換の流れ(イメージ図)
XML文書
<person>
<name>太郎</name>
<age>30</age>
</person>
│
▼
xml_parse_into_struct($parser, $xml, $values, $index)
│
┌──────────┴───────────────────────────┐
│ $values にはフラットな配列として │
│ 各要素の "tag", "type", "level", "value" が │
│ 順番に格納される │
└──────────┬───────────────────────────┘
▼
$values = [
['tag' => 'PERSON', 'type' => 'open', 'level' => 1],
['tag' => 'NAME', 'type' => 'complete', 'level' => 2, 'value' => '太郎'],
['tag' => 'AGE', 'type' => 'complete', 'level' => 2, 'value' => '30'],
['tag' => 'PERSON', 'type' => 'close', 'level' => 1],
]
$index = [
'PERSON' => [0, 3],
'NAME' => [1],
'AGE' => [2],
]
ポイントは、xml_parse_into_struct() の出力がネストした階層構造ではなく、フラットな配列のリストであるという点です。各要素には level(階層の深さ)というキーが含まれているため、この値を使えば元の階層構造を再構築することも可能ですが、そのままでは「親要素の中の子要素」というアクセスは直接的にはできません。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicStructParser
{
public function parse(string $xml): array
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values, $index);
xml_parser_free($parser);
return ['values' => $values, 'index' => $index];
}
}
$parser = new BasicStructParser();
$result = $parser->parse('<person><name>太郎</name><age>30</age></person>');
print_r($result['values']);
例2:indexを使って特定タグの値だけを素早く取り出すクラス
<?php
class TagValueExtractor
{
/**
* 第4引数の$indexを利用することで、
* 特定タグの値に高速にアクセスできる
*/
public function extractTagValue(string $xml, string $tagName): ?string
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values, $index);
xml_parser_free($parser);
$upperTag = strtoupper($tagName); // タグ名は大文字化されて格納される
if (!isset($index[$upperTag])) {
return null;
}
$firstIndex = $index[$upperTag][0];
return $values[$firstIndex]['value'] ?? null;
}
}
$extractor = new TagValueExtractor();
echo $extractor->extractTagValue('<person><name>花子</name><age>25</age></person>', 'name') . PHP_EOL;
例3:フラットな配列から連想配列(単純な構造向け)に変換するクラス
<?php
class SimpleStructToArrayConverter
{
/**
* ネストしない単純なXML構造を前提に、
* "タグ名 => 値" のシンプルな連想配列に変換する
*/
public function convert(string $xml): array
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);
$result = [];
foreach ($values as $item) {
if ($item['type'] === 'complete') {
$result[strtolower($item['tag'])] = $item['value'] ?? '';
}
}
return $result;
}
}
$converter = new SimpleStructToArrayConverter();
print_r($converter->convert('<config><host>localhost</host><port>8080</port></config>'));
// ['host' => 'localhost', 'port' => '8080']
例4:属性情報も含めて解析結果を利用するクラス
<?php
class AttributeAwareStructParser
{
/**
* openタイプの要素にはattributesキーが含まれることがあるため、
* それも含めて情報を収集する
*/
public function extractWithAttributes(string $xml): array
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);
$result = [];
foreach ($values as $item) {
if (isset($item['attributes'])) {
$result[] = [
'tag' => $item['tag'],
'attributes' => $item['attributes'],
];
}
}
return $result;
}
}
$parser = new AttributeAwareStructParser();
print_r($parser->extractWithAttributes('<item id="101" category="book">タイトル</item>'));
例5:case foldingを無効化して大文字小文字を保持するクラス
<?php
class CasePreservingStructParser
{
/**
* xml_parse()と同様、xml_parser_set_option()で
* case foldingを無効化すれば元のタグ名を保持できる
*/
public function parse(string $xml): array
{
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);
return $values;
}
}
$parser = new CasePreservingStructParser();
$result = $parser->parse('<Person><FirstName>Taro</FirstName></Person>');
print_r(array_column($result, 'tag'));
// ['Person', 'FirstName', 'FirstName', 'Person'] (大文字小文字が保持される)
例6:levelを利用して簡易的な階層構造を再構築するクラス
<?php
class NestedStructureRebuilder
{
/**
* 各要素の"level"情報を使って、
* フラットな配列から簡易的な階層構造を復元する
*/
public function rebuild(string $xml): array
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);
$stack = [[]];
foreach ($values as $item) {
if ($item['type'] === 'open') {
$stack[] = [];
} elseif ($item['type'] === 'close') {
$child = array_pop($stack);
$stack[count($stack) - 1][strtolower($item['tag'])] = $child;
} elseif ($item['type'] === 'complete') {
$stack[count($stack) - 1][strtolower($item['tag'])] = $item['value'] ?? '';
}
}
return $stack[0];
}
}
$rebuilder = new NestedStructureRebuilder();
print_r($rebuilder->rebuild('<order><customer><name>太郎</name></customer><total>5000</total></order>'));
例7:外部フィードのXMLを一括で読み込むリーダークラス
<?php
class FeedItemReader
{
/**
* RSSフィードのような単純な繰り返し構造を持つXMLから、
* item要素ごとの値を抽出する(簡易版のため厳密なRSSパーサーの代替ではない点に注意)
*/
public function readItems(string $xml): array
{
$parser = xml_parser_create();
xml_parse_into_struct($parser, $xml, $values);
xml_parser_free($parser);
$items = [];
$current = [];
$inItem = false;
foreach ($values as $entry) {
if ($entry['tag'] === 'ITEM' && $entry['type'] === 'open') {
$inItem = true;
$current = [];
} elseif ($entry['tag'] === 'ITEM' && $entry['type'] === 'close') {
$inItem = false;
$items[] = $current;
} elseif ($inItem && $entry['type'] === 'complete') {
$current[strtolower($entry['tag'])] = $entry['value'] ?? '';
}
}
return $items;
}
}
$reader = new FeedItemReader();
$xml = '<items><item><title>記事A</title><link>https://example.com/a</link></item></items>';
print_r($reader->readItems($xml));
関連関数との比較
| 関数/クラス | 役割 | xml_parse_into_structとの違い |
|---|---|---|
xml_parse_into_struct() | XMLを1回の呼び出しでフラットな配列に変換 | 本記事の対象。ハンドラの個別登録が不要で手軽 |
xml_parse() | ハンドラを登録してイベント駆動で解析 | より柔軟だが、コールバックの実装が必要 |
simplexml_load_string() | XMLをオブジェクトツリーとして読み込む | 階層構造を直感的にプロパティアクセスできる |
DOMDocument | XMLをDOMツリーとして読み込み・操作する | より高機能だが、その分コード量も増える |
json_decode() | JSON文字列を配列/オブジェクトに変換 | 対象のフォーマットが異なるが、「構造化データへの変換」という目的は共通 |
よくある落とし穴(注意点)
- 出力がフラットな配列であり、階層構造ではない
xml_parse_into_struct()の結果はネストした配列ではなく、levelキーを持つフラットなリストです。複雑な階層構造を持つXMLをそのままオブジェクトのように扱いたい場合は、SimpleXMLElementの方が適していることがあります。 - タグ名がデフォルトで大文字に変換される
xml_parse()と同様、Expatパーサーのデフォルト設定ではタグ名が大文字化されます。元の大小文字を保持したい場合はXML_OPTION_CASE_FOLDINGを無効化する必要があります(例5を参照)。 typeの値(open/close/complete/cdata)の意味を正しく理解する 要素に子要素やテキストが存在するかどうかによってtypeの値が変わります。特にテキストを含まない空要素や、開始・終了タグが分かれているケースなど、typeの分岐を正しく処理しないと値の取得漏れにつながります(例6・例7のようにopen/close/completeを意識した実装が必要です)。- 属性を持つ要素の扱いを忘れる 要素が属性を持つ場合、
$valuesの該当エントリにattributesキーが追加されます。属性情報も取得したい場合は、isset($item['attributes'])のチェックを忘れずに行いましょう(例4を参照)。 - 大容量XMLに対しては
xml_parse()より不向きな場合があるxml_parse_into_struct()は結果をすべて配列としてメモリ上に保持するため、非常に大きなXMLファイルを扱う場合は、xml_parse()を使ったストリーミング処理(コールバックベースの逐次処理)の方がメモリ効率に優れます。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | XML文書を1回の呼び出しで、フラットな構造を持つ配列に変換する |
| 主な用途 | 単純な構造のXML(設定ファイル、フィードなど)の手軽な読み込み |
xml_parse()との違い | ハンドラの個別登録が不要で、より簡潔に書ける |
| 出力の特徴 | ネストしないフラットな配列。level情報を使えば階層を再構築できる |
| 注意点 | タグ名の大文字化、typeの分岐処理、属性情報の扱い、大容量データでのメモリ効率 |
xml_parse_into_struct() は、xml_parse() に比べて手軽にXMLを配列として扱いたい場合に便利な関数です。ただし出力がフラットな構造である点を理解し、必要に応じて level 情報を使った階層再構築のロジックを実装することで、単純な設定ファイルやフィードデータの読み込みに効果的に活用できます。
