はじめに
これまでの記事で、XML解析中の位置情報を取得する xml_get_current_line_number()、xml_get_current_column_number()、xml_get_current_byte_index() を解説し、その中でエラー内容を文字列に変換する xml_error_string() にも度々登場してもらいました。今回は、その xml_error_string() に渡すエラーコードそのものを取得する関数、xml_get_error_code() を正面から解説します。
xml_get_error_code() は、xml_parse() によるXML解析が失敗した際に、その失敗の原因を表す整数のエラーコードを取得するための関数です。この関数単体では人間にとって意味のある情報にはなりませんが、xml_error_string() や、コードごとの個別分岐処理と組み合わせることで、堅牢なXMLエラーハンドリングの土台となります。本記事では基本的な使い方から実践的な活用例まで詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | xml_get_error_code() |
| 所属拡張 | XML Parser拡張(Expatベース、標準で有効) |
| シグネチャ | xml_get_error_code(XMLParser $parser): int |
| 引数 | $parser — xml_parser_create() で生成したパーサーインスタンス |
| 戻り値 | エラーコードを表す整数(XML_ERROR_* 定数のいずれか) |
| 対応バージョン | PHP 4以降(PHP 8.0以降は引数の型がXMLParserオブジェクトに変更) |
| エラーなし時 | XML_ERROR_NONE(値は0)が返る |
| 関連関数 | xml_error_string()(コードを説明文に変換) |
エラーハンドリングの全体像(イメージ図)
XML解析処理
xml_parse($parser, $xml, true)
│
│ 戻り値が0(失敗)の場合
▼
┌───────────────────────────┐
│ xml_get_error_code($parser) │ ← ★この記事の対象
│ 例: 4 (整数のエラーコード) │
└───────────┬───────────────┘
│
┌──────────┴──────────────┐
▼ ▼
xml_error_string($code) switch文などで
→ "mismatched tag" コードごとに個別処理
(人間可読な文字列に変換) (プログラム的な分岐)
ポイントは、xml_get_error_code() が返す値がそのままでは人間には読めない整数であるという点です。この整数は、XML_ERROR_TAG_MISMATCH のような定数と比較して「どんな種類の問題だったか」をプログラム的に判定するために使うのが本来の目的であり、人間向けの表示には xml_error_string() を併用します。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicErrorCodeDemo
{
public function checkError(string $xml): void
{
$parser = xml_parser_create();
if (!xml_parse($parser, $xml, true)) {
// エラーコード(整数)を取得する
$code = xml_get_error_code($parser);
echo "エラーコード: {$code}" . PHP_EOL;
} else {
echo '解析成功' . PHP_EOL;
}
xml_parser_free($parser);
}
}
$demo = new BasicErrorCodeDemo();
$demo->checkError('<root><item>value</root>');
例2:エラーコードに応じて処理を分岐するクラス
<?php
class ErrorCodeBasedHandler
{
/**
* エラーの種類ごとに異なる対応(リトライ、ログ記録、通知など)を
* 分岐させる例
*/
public function handle(string $xml): string
{
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true)) {
xml_parser_free($parser);
return '成功';
}
$code = xml_get_error_code($parser);
xml_parser_free($parser);
return match ($code) {
XML_ERROR_TAG_MISMATCH => 'タグの不一致です。自動修復を試みます。',
XML_ERROR_NO_ELEMENTS => '空のXMLです。処理をスキップします。',
XML_ERROR_INVALID_TOKEN => '不正な文字が含まれています。エンコーディングを確認してください。',
default => '未分類のエラーです: ' . xml_error_string($code),
};
}
}
$handler = new ErrorCodeBasedHandler();
echo $handler->handle('<root><item>value</root>') . PHP_EOL;
echo $handler->handle('') . PHP_EOL;
例3:成功・失敗をエラーコードで明確に判定するバリデーター
<?php
class XmlWellFormednessValidator
{
/**
* XML_ERROR_NONE(値は0)かどうかで
* 明示的に成功・失敗を判定する
*/
public function isWellFormed(string $xml): bool
{
$parser = xml_parser_create();
xml_parse($parser, $xml, true);
$code = xml_get_error_code($parser);
xml_parser_free($parser);
return $code === XML_ERROR_NONE;
}
}
$validator = new XmlWellFormednessValidator();
var_dump($validator->isWellFormed('<root><item>value</item></root>')); // true
var_dump($validator->isWellFormed('<root><item>value</root>')); // false
例4:リトライ可能なエラーとそうでないエラーを区別する堅牢な処理
<?php
class RetryableXmlParser
{
private const RETRYABLE_CODES = [
XML_ERROR_NO_MEMORY, // 一時的なメモリ不足など
];
/**
* エラーコードに基づいて、
* リトライすべきか即座に諦めるべきかを判断する
*/
public function parseWithRetryLogic(string $xml, int $maxRetries = 3): array
{
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
$parser = xml_parser_create();
$success = xml_parse($parser, $xml, true);
$code = xml_get_error_code($parser);
xml_parser_free($parser);
if ($success) {
return ['success' => true, 'attempts' => $attempt];
}
if (!in_array($code, self::RETRYABLE_CODES, true)) {
// リトライ対象外のエラーは即座に打ち切る
return ['success' => false, 'code' => $code, 'attempts' => $attempt, 'retried' => false];
}
}
return ['success' => false, 'attempts' => $maxRetries, 'retried' => true];
}
}
$parser = new RetryableXmlParser();
print_r($parser->parseWithRetryLogic('<root><item>value</root>'));
例5:エラーコードの集計統計を取るバッチ検証ツール
<?php
class XmlErrorStatisticsCollector
{
/**
* 大量のXMLファイルを検証し、
* どのエラーコードがどれだけ発生したかを集計する
*/
public function collectStatistics(array $filePaths): array
{
$stats = [];
foreach ($filePaths as $path) {
$content = file_get_contents($path);
$parser = xml_parser_create();
xml_parse($parser, $content, true);
$code = xml_get_error_code($parser);
xml_parser_free($parser);
$label = $code === XML_ERROR_NONE ? '成功' : xml_error_string($code);
$stats[$label] = ($stats[$label] ?? 0) + 1;
}
return $stats;
}
}
// $collector = new XmlErrorStatisticsCollector();
// print_r($collector->collectStatistics(['/tmp/a.xml', '/tmp/b.xml', '/tmp/c.xml']));
例6:カスタム例外にエラーコードを保持させる設計
<?php
class XmlParsingException extends RuntimeException
{
public function __construct(
string $message,
private readonly int $xmlErrorCode,
private readonly int $line
) {
parent::__construct($message);
}
public function getXmlErrorCode(): int
{
return $this->xmlErrorCode;
}
public function getXmlErrorLine(): int
{
return $this->line;
}
}
class ExceptionThrowingXmlParser
{
public function parse(string $xml): void
{
$parser = xml_parser_create();
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
$line = xml_get_current_line_number($parser);
$message = xml_error_string($code);
xml_parser_free($parser);
// エラーコード自体を例外オブジェクトに保持させることで、
// 呼び出し元でプログラム的な分岐が可能になる
throw new XmlParsingException($message, $code, $line);
}
xml_parser_free($parser);
}
}
$parser = new ExceptionThrowingXmlParser();
try {
$parser->parse('<root><item>value</root>');
} catch (XmlParsingException $e) {
echo "コード{$e->getXmlErrorCode()}: {$e->getMessage()} ({$e->getXmlErrorLine()}行目)" . PHP_EOL;
}
例7:主要なエラーコード定数の一覧をまとめて確認するリファレンスツール
<?php
class ErrorCodeReferenceBuilder
{
/**
* よく遭遇する主要なXML_ERROR_*定数と、
* その値・説明の対応表を作成する
*/
public function build(): array
{
$constants = [
'XML_ERROR_NONE' => XML_ERROR_NONE,
'XML_ERROR_NO_MEMORY' => XML_ERROR_NO_MEMORY,
'XML_ERROR_SYNTAX' => XML_ERROR_SYNTAX,
'XML_ERROR_NO_ELEMENTS' => XML_ERROR_NO_ELEMENTS,
'XML_ERROR_INVALID_TOKEN' => XML_ERROR_INVALID_TOKEN,
'XML_ERROR_UNCLOSED_TOKEN' => XML_ERROR_UNCLOSED_TOKEN,
'XML_ERROR_TAG_MISMATCH' => XML_ERROR_TAG_MISMATCH,
'XML_ERROR_DUPLICATE_ATTRIBUTE' => XML_ERROR_DUPLICATE_ATTRIBUTE,
];
$reference = [];
foreach ($constants as $name => $value) {
$reference[$name] = [
'value' => $value,
'description' => xml_error_string($value),
];
}
return $reference;
}
}
$builder = new ErrorCodeReferenceBuilder();
print_r($builder->build());
関連関数との比較
| 関数 | 役割 | xml_get_error_codeとの違い |
|---|---|---|
xml_get_error_code() | 現在のエラーコード(整数)を取得 | 本記事の対象。プログラム的な分岐判定に使う |
xml_error_string() | エラーコードを人間が読める説明文に変換 | 取得したコードを文字列化するための関数 |
xml_get_current_line_number() | エラー発生時の行番号を取得 | エラーの「種類」ではなく「発生位置」を扱う |
xml_parse() | XMLを解析する(戻り値でエラーの有無を判定) | エラーコードそのものではなく、成功/失敗の真偽値を返す |
libxml_get_last_error() | libxml系パーサーの最後のエラー情報を取得 | Expat系ではなく、SimpleXML/DOMDocument向けの同種機能 |
よくある落とし穴(注意点)
xml_parse()の戻り値チェックを省略しないxml_get_error_code()は、あくまで「最後の解析結果に関するエラーコード」を返す関数であり、xml_parse()の戻り値をチェックせずにいきなり呼び出すと、成功時にもXML_ERROR_NONE(0)が返るだけで、エラー処理のロジックとして意味を成しません。必ずxml_parse()の戻り値と併用しましょう。XML_ERROR_NONEの値が0であることを利用した判定に注意する PHPでは0は falsy な値として扱われるため、if (xml_get_error_code($parser))のような書き方をすると意図通りに動作しますが、読み手にとって分かりにくいコードになりがちです。=== XML_ERROR_NONEのように明示的に比較する方が可読性が高くなります(例3を参照)。- エラーコードの数値だけをログに残して、後で調査に困る 整数のエラーコードだけをログに記録すると、後から「このコードは何を意味していたか」を調べる手間がかかります。
xml_error_string()と組み合わせて、コードと説明文の両方を記録しておくのが望ましいです(例5を参照)。 - すべてのエラーコードを網羅的に
match/switchで処理しようとするXML_ERROR_*定数は多数存在するため、すべてを個別にハンドリングしようとすると保守性が低下します。重要なケースのみを個別処理し、それ以外はdefaultとしてまとめて扱う設計が現実的です(例2を参照)。 - PHP 8.0以降での型変更に注意する PHP 8.0以降、
xml_parser_create()の戻り値がリソース型からXMLParserオブジェクトに変更されています。is_resource()によるチェックを行っている古いコードでは、この変更に対応した修正が必要になる場合があります。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | XML解析失敗時のエラーコード(整数)を取得する |
| 主な用途 | エラーの種類に応じた処理分岐、統計収集、例外へのエラーコード保持 |
| 併用する関数 | xml_error_string()(人間向けの説明文への変換) |
| 成功時の値 | XML_ERROR_NONE(値は0) |
| 注意点 | xml_parse()の戻り値チェックとの併用、0の扱いの明確化、ログには説明文も残すこと |
xml_get_error_code() は、XML解析エラーの「種類」をプログラム的に判定するための出発点となる関数です。単体では意味を持ちにくい整数値ですが、xml_error_string() との組み合わせや、エラーコードごとの分岐処理を通じて、堅牢で保守性の高いXMLエラーハンドリングを実現できます。
