はじめに
PHPの標準XMLパーサー(Expatベースの xml_parser_create() などで構成される一連の関数群)を使ってXML文書を解析していると、不正な形式のXMLに遭遇して解析が失敗することがあります。このとき、xml_get_error_code() を使えばエラーの原因を表す数値コードを取得できますが、その数値だけでは「一体何が問題だったのか」を人間が直感的に理解することはできません。
そこで使うのが xml_error_string() 関数です。この関数はエラーコードを受け取り、"not well-formed (invalid token)" のような、人間が読める英語の説明文に変換してくれます。エラーログの出力やデバッグ時の原因調査において、XMLパーサー関連のエラーハンドリングに欠かせない関数です。本記事では基本的な使い方から実践的な活用例まで詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | xml_error_string() |
| 所属拡張 | XML Parser拡張(Expatベース、標準で有効) |
| シグネチャ | xml_error_string(int $code): ?string |
| 引数 | $code — エラーコード(XML_ERROR_* 定数、またはxml_get_error_code()の戻り値) |
| 戻り値 | エラー内容を表す説明文字列。該当しない場合は null |
| 対応バージョン | PHP 4以降 |
| 関連関数 | xml_get_error_code()(エラーコード取得)、xml_get_current_line_number()(エラー発生行の取得) |
エラーハンドリングの流れ(イメージ図)
不正なXML文書
"<root><item>value</root>" (閉じタグが対応していない)
│
▼
xml_parse($parser, $xml)
│
│ 戻り値が0(失敗)
▼
┌───────────────────────────┐
│ xml_get_error_code($parser) │ ← エラーコード(整数)を取得
│ 例: 4 (XML_ERROR_TAG_MISMATCH) │
└───────────┬───────────────┘
▼
┌───────────────────────────┐
│ xml_error_string($code) │ ← ★この記事の対象
│ 例: "mismatched tag" │ ← 人間が読める説明文に変換
└───────────────────────────┘
ポイントは、xml_error_string() が単体では使わず、必ず xml_get_error_code() とセットで使われるという点です。エラーコードという「機械にとって扱いやすい数値」を、人間が理解しやすい「説明文」に変換するための橋渡し役を担っています。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicXmlErrorDemo
{
public function parseAndReport(string $xml): void
{
$parser = xml_parser_create();
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
// エラーコードを人間が読める説明文に変換する
$message = xml_error_string($code);
echo "XML解析エラー: {$message}" . PHP_EOL;
} else {
echo 'XML解析に成功しました' . PHP_EOL;
}
xml_parser_free($parser);
}
}
$demo = new BasicXmlErrorDemo();
$demo->parseAndReport('<root><item>value</root>'); // 閉じタグの不一致
例2:エラー発生位置(行・列)と合わせて詳細なレポートを作るクラス
<?php
class DetailedXmlErrorReporter
{
/**
* xml_error_string()に加えて、
* xml_get_current_line_number()等で発生位置も特定する
*/
public function analyze(string $xml): ?array
{
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true)) {
xml_parser_free($parser);
return null; // エラーなし
}
$report = [
'code' => xml_get_error_code($parser),
'message' => xml_error_string(xml_get_error_code($parser)),
'line' => xml_get_current_line_number($parser),
'column' => xml_get_current_column_number($parser),
'byte' => xml_get_current_byte_index($parser),
];
xml_parser_free($parser);
return $report;
}
}
$reporter = new DetailedXmlErrorReporter();
print_r($reporter->analyze("<root>\n <item>value</root>"));
例3:例外に変換してモダンなエラーハンドリングに乗せるクラス
<?php
class XmlParseException extends RuntimeException
{
}
class ExceptionBasedXmlParser
{
/**
* XMLパーサーの古いスタイルのエラーハンドリングを、
* 現代的な例外ベースのハンドリングに変換するラッパー
*/
public function parse(string $xml): array
{
$parser = xml_parser_create();
$result = [];
xml_set_element_handler(
$parser,
function ($parser, $name, $attrs) use (&$result) {
$result[] = ['tag' => $name, 'attrs' => $attrs];
},
fn () => null
);
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
$message = xml_error_string($code);
$line = xml_get_current_line_number($parser);
xml_parser_free($parser);
throw new XmlParseException("{$line}行目: {$message}", $code);
}
xml_parser_free($parser);
return $result;
}
}
$parser = new ExceptionBasedXmlParser();
try {
print_r($parser->parse('<root><a/><b></root>'));
} catch (XmlParseException $e) {
echo '解析失敗: ' . $e->getMessage() . PHP_EOL;
}
例4:外部から受け取ったXMLファイルの一括バリデーションツール
<?php
class XmlBatchValidator
{
/**
* 複数のXMLファイルをまとめて検証し、
* エラーがあったファイルとその理由を一覧化する
*/
public function validateFiles(array $filePaths): array
{
$results = [];
foreach ($filePaths as $path) {
$content = file_get_contents($path);
$parser = xml_parser_create();
if (xml_parse($parser, $content, true)) {
$results[$path] = 'OK';
} else {
$code = xml_get_error_code($parser);
$results[$path] = xml_error_string($code) . '(' . xml_get_current_line_number($parser) . '行目)';
}
xml_parser_free($parser);
}
return $results;
}
}
$validator = new XmlBatchValidator();
// print_r($validator->validateFiles(['/tmp/data1.xml', '/tmp/data2.xml']));
例5:エラーコード一覧を人間が読める形で出力するデバッグツール
<?php
class XmlErrorCodeReference
{
/**
* よく遭遇する主要なXML_ERROR_*定数について、
* 対応する説明文の一覧表を生成する
*/
public function buildReference(): array
{
$codes = [
XML_ERROR_NONE,
XML_ERROR_NO_MEMORY,
XML_ERROR_SYNTAX,
XML_ERROR_NO_ELEMENTS,
XML_ERROR_INVALID_TOKEN,
XML_ERROR_UNCLOSED_TOKEN,
XML_ERROR_TAG_MISMATCH,
XML_ERROR_DUPLICATE_ATTRIBUTE,
];
$reference = [];
foreach ($codes as $code) {
$reference[$code] = xml_error_string($code);
}
return $reference;
}
}
$reference = new XmlErrorCodeReference();
print_r($reference->buildReference());
例6:ログシステムと連携したXML解析エラーの記録
<?php
class XmlParseErrorLogger
{
public function __construct(private string $logFilePath)
{
}
/**
* XML解析に失敗した際に、xml_error_string()の結果を
* 構造化されたログとして記録する
*/
public function parseWithLogging(string $xml, string $sourceLabel): bool
{
$parser = xml_parser_create();
$success = xml_parse($parser, $xml, true);
if (!$success) {
$entry = sprintf(
"[%s] source=%s code=%d message=%s line=%d\n",
date('Y-m-d H:i:s'),
$sourceLabel,
xml_get_error_code($parser),
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)
);
file_put_contents($this->logFilePath, $entry, FILE_APPEND);
}
xml_parser_free($parser);
return $success;
}
}
$logger = new XmlParseErrorLogger('/tmp/xml_errors.log');
$logger->parseWithLogging('<root><a></root>', 'external_feed_import');
例7:ユーザー向けとエンジニア向けでメッセージを出し分けるクラス
<?php
class UserFriendlyXmlErrorTranslator
{
/**
* xml_error_string()が返す英語の技術的な説明文を、
* エンドユーザー向けの分かりやすい日本語メッセージに変換する
*/
private array $friendlyMessages = [
XML_ERROR_TAG_MISMATCH => 'タグの開始と終了が対応していません。',
XML_ERROR_UNCLOSED_TOKEN => '閉じられていないタグまたは属性があります。',
XML_ERROR_INVALID_TOKEN => '不正な文字が含まれています。',
];
public function translate(int $code): string
{
// 定義済みメッセージがあればそれを、なければ元の英語説明文を返す
return $this->friendlyMessages[$code] ?? xml_error_string($code) ?? '不明なエラーです。';
}
}
$translator = new UserFriendlyXmlErrorTranslator();
echo $translator->translate(XML_ERROR_TAG_MISMATCH) . PHP_EOL;
echo $translator->translate(XML_ERROR_NO_MEMORY) . PHP_EOL; // 定義外のため英語のまま
関連関数との比較
| 関数 | 役割 | xml_error_stringとの違い |
|---|---|---|
xml_error_string() | エラーコードを説明文字列に変換 | 本記事の対象。単体ではなく他のXML関数と組み合わせて使う |
xml_get_error_code() | パーサーの現在のエラーコードを取得 | xml_error_string()に渡す整数を取得するための関数 |
xml_get_current_line_number() | エラー発生時点の行番号を取得 | エラーの「内容」ではなく「発生位置」を特定する |
xml_get_current_column_number() | エラー発生時点の列番号を取得 | 同上、より詳細な発生位置の特定に使う |
libxml_get_errors() | libxml系(SimpleXML/DOMDocument)のエラー一覧取得 | Expat系ではなく、より高機能なlibxml系パーサーのエラー処理に使う |
よくある落とし穴(注意点)
- エラーコードなしで呼び出しても意味がない
xml_error_string()は単体でエラーの有無を判定する関数ではなく、あくまで既に取得したエラーコードを変換するための関数です。必ずxml_get_error_code()とセットで使う必要があります。 - 返される文字列は英語である
xml_error_string()が返すメッセージは英語の技術的な説明文であり、日本語のユーザー向けメッセージが必要な場合は、例7のように独自の翻訳テーブルを用意する必要があります。 xml_parse()の第3引数(is_final)を忘れる 分割してデータを渡す場合を除き、最後のチャンクではxml_parse($parser, $data, true)のように第3引数にtrueを指定してパースを終了させる必要があります。これを忘れると、途中で切れたXMLとして誤ったエラー判定がされることがあります。- 古いExpatベースの関数群と、より新しいlibxml系(SimpleXML/DOMDocument)を混同する
xml_error_string()はExpatベースのxml_parser_create()系専用の関数です。simplexml_load_string()やDOMDocumentを使う場合のエラー取得には、代わりにlibxml_get_errors()を使う必要があります。 - パーサーリソースの解放を忘れる
xml_parser_create()で生成したパーサーは、使い終わったらxml_parser_free()で明示的に解放するのが望ましい作法です。大量のXMLを処理するバッチ処理などでは、解放漏れがメモリ使用量の増大につながることがあります。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | XMLパーサーのエラーコードを、人間が読める説明文字列に変換する |
| 主な用途 | XML解析失敗時のエラーログ出力、デバッグ時の原因調査、バリデーションツールの実装 |
| セットで使う関数 | xml_get_error_code()(コード取得)、xml_get_current_line_number()(発生位置取得) |
| 出力される文字列 | 英語の技術的な説明文(日本語化には独自の翻訳テーブルが必要) |
| 注意点 | 単体では使えないこと、Expat系専用であること(libxml系にはlibxml_get_errors()を使う) |
xml_error_string() は、XMLパーサーのエラー処理において「原因を人間が理解できる形にする」という重要な役割を果たす関数です。xml_get_error_code() と組み合わせ、必要に応じてユーザー向けの分かりやすいメッセージへの変換も取り入れることで、堅牢で親切なXML処理機能を実装できます。
