はじめに
これまでの記事で、xml_parse() や xml_parse_into_struct() といったXML解析の中心的な関数を数多く解説してきましたが、そのすべての出発点となっているのが、今回取り上げる xml_parser_create() です。この関数を呼び出すことで初めて、一連のExpatベースのXML処理関数群が使える「パーサーインスタンス」が手に入ります。
xml_parser_create() は、名前の通りXMLパーサーを生成するための関数です。一見単純な関数に見えますが、引数で指定する文字エンコーディングの扱いや、似た名前を持つ xml_parser_create_ns()(名前空間対応版)との違いなど、正しく理解しておくべきポイントがいくつかあります。本記事では、この関数の基本的な役割と、実践的な活用パターンを詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | xml_parser_create() |
| 所属拡張 | XML Parser拡張(Expatベース、標準で有効) |
| シグネチャ | xml_parser_create(?string $encoding = null): XMLParser |
| 引数 | $encoding — 入力データのエンコーディング(省略時はXML宣言から自動判定) |
| 戻り値 | XMLパーサーインスタンス(XMLParserオブジェクト) |
| 対応バージョン | PHP 4以降(PHP 8.0以降は戻り値がリソース型からXMLParserオブジェクトに変更) |
| 対応エンコーディング | UTF-8, ISO-8859-1, US-ASCII |
| 関連関数 | xml_parser_create_ns()(名前空間対応版)、xml_parser_free()(解放) |
全体像(イメージ図)
一連のXML処理の流れ
xml_parser_create() ← ★この記事の対象
(パーサーインスタンスを生成)
│
▼
xml_set_element_handler() ← ハンドラを登録(xml_parse()を使う場合)
│
▼
xml_parse() / xml_parse_into_struct()
(実際の解析処理を実行)
│
▼
xml_parser_free() ← パーサーを解放
ポイントは、xml_parser_create() が生成するのはあくまで「空の」パーサーインスタンスであるという点です。この時点ではまだXMLの中身は一切解析されておらず、この後に続く xml_set_element_handler() などでのハンドラ登録、そして xml_parse() の呼び出しがあって初めて、実際の解析処理が始まります。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicParserFactory
{
public function createDefault(): XMLParser
{
// エンコーディングを省略すると、XML宣言から自動判定される
return xml_parser_create();
}
public function createWithEncoding(string $encoding): XMLParser
{
return xml_parser_create($encoding);
}
}
$factory = new BasicParserFactory();
$parser = $factory->createDefault();
var_dump($parser instanceof XMLParser); // true (PHP 8.0以降)
xml_parser_free($parser);
例2:生成から解放までをカプセル化した安全なラッパークラス
<?php
class SafeXmlParserWrapper
{
private XMLParser $parser;
public function __construct(?string $encoding = null)
{
$this->parser = xml_parser_create($encoding);
}
public function getParser(): XMLParser
{
return $this->parser;
}
/**
* デストラクタでパーサーの解放を保証する
*/
public function __destruct()
{
xml_parser_free($this->parser);
}
}
$wrapper = new SafeXmlParserWrapper('UTF-8');
xml_set_element_handler(
$wrapper->getParser(),
fn ($p, $name) => print("開始: {$name}\n"),
fn ($p, $name) => print("終了: {$name}\n")
);
xml_parse($wrapper->getParser(), '<root><item/></root>', true);
例3:エンコーディングを明示的に指定して非UTF-8データを扱う
<?php
class LegacyEncodingXmlParser
{
/**
* 古いシステムから受け取るISO-8859-1形式のXMLデータを
* 明示的にエンコーディング指定して解析する
*/
public function parseLegacyXml(string $xml): array
{
$parser = xml_parser_create('ISO-8859-1');
$result = [];
xml_set_character_data_handler($parser, function ($p, $data) use (&$result) {
$trimmed = trim($data);
if ($trimmed !== '') {
$result[] = $trimmed;
}
});
xml_parse($parser, $xml, true);
xml_parser_free($parser);
return $result;
}
}
$parser = new LegacyEncodingXmlParser();
print_r($parser->parseLegacyXml("<root><name>caf\xe9</name></root>"));
例4:複数のXML文書を並行して処理するためのパーサープール
<?php
class XmlParserPool
{
/** @var array<string, XMLParser> */
private array $parsers = [];
/**
* 複数の独立したXML文書を扱う際、
* それぞれに専用のパーサーインスタンスを割り当てる
*/
public function createParser(string $key, ?string $encoding = null): XMLParser
{
$parser = xml_parser_create($encoding);
$this->parsers[$key] = $parser;
return $parser;
}
public function freeAll(): void
{
foreach ($this->parsers as $parser) {
xml_parser_free($parser);
}
$this->parsers = [];
}
}
$pool = new XmlParserPool();
$parserA = $pool->createParser('feedA');
$parserB = $pool->createParser('feedB');
xml_parse($parserA, '<a><item/></a>', true);
xml_parse($parserB, '<b><item/></b>', true);
$pool->freeAll();
例5:xml_parser_createとxml_parser_create_nsの違いを比較する
<?php
class NamespaceSupportComparator
{
/**
* 名前空間を含むXMLを扱う場合、
* xml_parser_create_ns()を使うとプレフィックスとローカル名を分離できる
*/
public function compareTagHandling(string $xml): array
{
$tagsWithoutNs = [];
$parserA = xml_parser_create();
xml_set_element_handler(
$parserA,
function ($p, $name) use (&$tagsWithoutNs) { $tagsWithoutNs[] = $name; },
fn () => null
);
xml_parse($parserA, $xml, true);
xml_parser_free($parserA);
$tagsWithNs = [];
$parserB = xml_parser_create_ns();
xml_set_element_handler(
$parserB,
function ($p, $name) use (&$tagsWithNs) { $tagsWithNs[] = $name; },
fn () => null
);
xml_parse($parserB, $xml, true);
xml_parser_free($parserB);
return ['without_ns' => $tagsWithoutNs, 'with_ns' => $tagsWithNs];
}
}
$comparator = new NamespaceSupportComparator();
print_r($comparator->compareTagHandling('<ns:root xmlns:ns="http://example.com"><ns:item/></ns:root>'));
例6:パーサー生成時にユーザーデータを紐付けて状態を管理する
<?php
class StatefulXmlParser
{
/**
* xml_parser_set_option()と組み合わせ、
* パーサー生成直後にオプションを設定しておく実践パターン
*/
public function createConfiguredParser(): XMLParser
{
$parser = xml_parser_create('UTF-8');
// 大文字小文字の変換を無効化
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
// 出力エンコーディングをUTF-8に統一
xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
// 要素間の空白を保持しない
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, true);
return $parser;
}
}
$factory = new StatefulXmlParser();
$parser = $factory->createConfiguredParser();
xml_parse($parser, '<Root> <Item>value</Item> </Root>', true);
xml_parser_free($parser);
例7:ファクトリーメソッドパターンで用途別のパーサーを使い分ける
<?php
class XmlParserFactory
{
/**
* 用途に応じて異なる設定のパーサーを生成する
* ファクトリーメソッドパターンの実装例
*/
public static function forRssFeed(): XMLParser
{
$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
return $parser;
}
public static function forConfigFile(): XMLParser
{
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, true);
return $parser;
}
}
$rssParser = XmlParserFactory::forRssFeed();
xml_parse($rssParser, '<rss><item><title>News</title></item></rss>', true);
xml_parser_free($rssParser);
関連関数との比較
| 関数 | 役割 | xml_parser_createとの違い |
|---|---|---|
xml_parser_create() | 名前空間非対応のパーサーを生成 | 本記事の対象。シンプルなXML処理向け |
xml_parser_create_ns() | 名前空間対応のパーサーを生成 | 要素名からプレフィックスとローカル名を分離して扱える |
xml_parser_free() | パーサーインスタンスを解放 | 生成したパーサーの後始末を行う対になる関数 |
xml_parser_set_option() | パーサーの動作オプションを設定 | 生成後に大文字小文字変換や空白除去などの挙動を調整する |
simplexml_load_string() | XML文字列をオブジェクトとして直接読み込む | パーサーの明示的な生成・解放が不要な、より簡易な代替手段 |
よくある落とし穴(注意点)
- パーサーの解放(
xml_parser_free())を忘れるxml_parser_create()で生成したパーサーは、使い終わったら明示的に解放するのが望ましい作法です。大量のXMLを処理するループの中で解放を忘れると、メモリ使用量が増大していく可能性があります(例2のようなデストラクタでの解放が有効です)。 - 名前空間を持つXMLに対して通常版を使ってしまう 名前空間プレフィックス(
ns:itemなど)を個別に扱いたい場合、通常のxml_parser_create()では要素名がプレフィックス込みの文字列としてそのまま渡されます。プレフィックスとローカル名を分離したい場合はxml_parser_create_ns()を使う必要があります(例5を参照)。 - PHP 8.0以降での戻り値の型変更に注意する PHP 8.0より前は
xml_parser_create()の戻り値がリソース型でしたが、PHP 8.0以降はXMLParserオブジェクトに変更されています。is_resource()による型チェックを行っている古いコードは、instanceof XMLParserのような形に修正が必要です。 - エンコーディング指定は「入力」データの解釈に関わるものだと理解する
xml_parser_create()の引数で指定するエンコーディングは、あくまで入力XMLの解釈に使われるものです。出力(コールバックに渡される文字列)のエンコーディングを制御したい場合は、別途xml_parser_set_option()でXML_OPTION_TARGET_ENCODINGを設定する必要があります(例6を参照)。 - 1つのパーサーインスタンスを複数のXML文書で使い回そうとする 基本的に1つのパーサーインスタンスは1つの解析セッション用です。異なるXML文書を解析したい場合は、それぞれに新しいパーサーインスタンスを生成するのが安全です(例4のようなプール管理が参考になります)。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | ExpatベースのXMLパーサーインスタンスを新規生成する |
| 主な用途 | xml_parse()やxml_parse_into_struct()を使う前の準備段階 |
| 名前空間対応 | 非対応。名前空間を扱いたい場合はxml_parser_create_ns()を使う |
| 対になる関数 | xml_parser_free()(解放) |
| 注意点 | 解放忘れによるメモリ増大、名前空間対応版との使い分け、PHP 8.0での型変更 |
xml_parser_create() は、一連のExpatベースXML処理のすべての起点となる、シンプルながら重要な関数です。生成したパーサーを適切に設定し、使い終わったら確実に解放するという基本を押さえることで、堅牢なXML処理機能を構築できます。
