【PHP8.x】xml_set_external_entity_ref_handler()関数の使い方
xml_set_external_entity_ref_handler関数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
xml_set_external_entity_ref_handler関数は、XMLパーサーが外部エンティティ参照を検出した際に呼び出されるコールバック関数を設定する関数です。外部エンティティ参照とは、XMLドキュメント内で外部にあるリソースを参照する仕組みであり、DTD(Document Type Definition)などで定義されます。この関数を使うことで、外部エンティティ参照の解決をカスタマイズし、外部リソースへのアクセス方法を制御できます。
具体的には、xml_set_external_entity_ref_handler関数は、XMLパーサーリソース、開始要素ハンドラ関数、終了要素ハンドラ関数という3つの引数を取ります。XMLパーサーリソースは、xml_parser_create関数などで作成されたXMLパーサーのインスタンスです。開始要素ハンドラ関数と終了要素ハンドラ関数は、それぞれ外部エンティティ参照の開始時と終了時に呼び出されるPHPの関数です。
これらのハンドラ関数は、外部エンティティ参照の名前、ベースURI、エンティティのシステムID、パブリックIDなどの情報を受け取ることができます。システムエンジニアは、これらの情報に基づいて、外部エンティティを解決し、必要なデータをXMLパーサーに提供する処理を記述します。外部エンティティ参照ハンドラを設定することで、XMLドキュメントの検証、データの読み込み、エラー処理などを柔軟に行うことが可能になります。セキュリティ上の観点からも、信頼できない外部ソースへのアクセスを制限するために、このハンドラを適切に設定することが重要です。
構文(syntax)
1xml_set_external_entity_ref_handler(XMLParser $parser, callable $handler): bool
引数(parameters)
XMLParser $parser, callable|string|null $handler
- XMLParser $parser: 処理対象のXMLパーサーオブジェクト
- callable|string|null $handler: 外部エンティティ参照を処理するコールバック関数、またはその関数名(文字列)、あるいは無効にする場合はnull
戻り値(return)
true
xml_set_external_entity_ref_handler関数は、外部エンティティ参照が発生した際に呼び出されるコールバック関数を設定します。設定が成功した場合は true を返します。
サンプルコード
PHP: xml_set_external_entity_ref_handler の使い方
1<?php 2 3/** 4 * XMLパーサーと外部エンティティ参照ハンドラの基本的な使用例。 5 * 6 * この関数は、XML文字列を解析し、要素の開始/終了、文字データ、 7 * そして特に外部エンティティ参照が検出された際のハンドラ設定方法を示します。 8 * システムエンジニアを目指す初心者向けに、具体的なXMLパースの流れを理解しやすくします。 9 * 10 * @param string $xmlString 解析するXML文字列。 11 * @return void 12 */ 13function parseXmlWithExternalEntityHandler(string $xmlString): void 14{ 15 // 1. XMLパーサーを作成します。 16 $parser = xml_parser_create(); 17 if ($parser === false) { 18 echo "XMLパーサーの作成に失敗しました。" . PHP_EOL; 19 return; 20 } 21 22 // 2. 外部エンティティ参照ハンドラを設定します。 23 // このハンドラは、DTD(Document Type Definition)内で<!ENTITY ... SYSTEM "uri"> 24 // または <!ENTITY ... PUBLIC "pubid" "uri"> の形式で外部エンティティが参照された際に呼び出されます。 25 // ハンドラは、参照された外部エンティティの内容をどのように解決するかをPHPに伝える役割を持ちます。 26 xml_set_external_entity_ref_handler( 27 $parser, 28 function ( 29 XMLParser $parser, 30 string $openEntityNames, // 現在開いているエンティティの名前(複数の場合、"root.sub.entity" のようにドット区切り) 31 string $base, // システムIDを解決するための基準URI 32 string $systemId, // 外部エンティティのシステムID (通常はファイルパスやURL) 33 ?string $publicId // 外部エンティティのパブリックID (DTDの識別子など、省略可能) 34 ): bool { 35 echo "--- 外部エンティティ参照ハンドラ呼び出し ---" . PHP_EOL; 36 echo "開いているエンティティ: '{$openEntityNames}'" . PHP_EOL; 37 echo "システムID: '{$systemId}'" . PHP_EOL; 38 echo "パブリックID: " . ($publicId ?? "なし") . PHP_EOL; 39 echo "このハンドラ内で、通常は'{$systemId}'からデータを読み込み、xml_parse_external_entity() でパースを継続します。" . PHP_EOL; 40 echo "今回はデモンストレーションのため、メッセージ表示のみで実際の読み込みは行いません。" . PHP_EOL; 41 42 // trueを返すと、PHPは外部エンティティの処理を継続しようとします。 43 // falseを返すと、エラーとして処理されます。 44 return true; 45 } 46 ); 47 48 // 3. 要素の開始ハンドラと終了ハンドラを設定します。 49 // これはXMLパースで最も一般的に使用されるハンドラで、キーワードにも関連します。 50 xml_set_element_handler( 51 $parser, 52 // 開始タグが見つかったときに呼び出される関数 53 function (XMLParser $parser, string $name, array $attributes): void { 54 echo "要素開始: <" . strtolower($name); 55 foreach ($attributes as $attrName => $attrValue) { 56 echo " {$attrName}=\"{$attrValue}\""; 57 } 58 echo ">" . PHP_EOL; 59 }, 60 // 終了タグが見つかったときに呼び出される関数 61 function (XMLParser $parser, string $name): void { 62 echo "要素終了: </" . strtolower($name) . ">" . PHP_EOL; 63 } 64 ); 65 66 // 4. 文字データ(要素間のテキスト)ハンドラを設定します。 67 xml_set_character_data_handler( 68 $parser, 69 function (XMLParser $parser, string $data): void { 70 $trimmedData = trim($data); 71 if (!empty($trimmedData)) { 72 echo "文字データ: '" . $trimmedData . "'" . PHP_EOL; 73 } 74 } 75 ); 76 77 // 5. XML文字列をパースします。 78 // 第3引数にtrueを渡すと、これが最後のデータチャンクであることを示します。 79 if (!xml_parse($parser, $xmlString, true)) { 80 // パース中にエラーが発生した場合、エラー情報を表示します。 81 $errorCode = xml_get_error_code($parser); 82 $errorString = xml_error_string($errorCode); 83 $line = xml_get_current_line_number($parser); 84 $column = xml_get_current_column_number($parser); 85 echo "XMLパースエラー: {$errorString} (コード: {$errorCode}) at line {$line}, column {$column}" . PHP_EOL; 86 } else { 87 echo "XMLパースが完了しました。" . PHP_EOL; 88 } 89 90 // 6. 使用済みパーサーを解放し、リソースをクリーンアップします。 91 xml_parser_free($parser); 92} 93 94// 外部エンティティ参照を含むXMLデータの例。 95// このDTDは、'my_external_data'という名前の外部エンティティを定義し、 96// "non_existent_data.xml" というシステムID(ファイルパスを想定)を参照しています。 97// 実際のファイルが存在しなくても、参照があるだけでハンドラが呼び出されます。 98$xmlData = <<<XML 99<?xml version="1.0" encoding="UTF-8"?> 100<!DOCTYPE document [ 101 <!ENTITY internal_greeting "Hello from an internal entity!"> 102 <!ENTITY my_external_data SYSTEM "non_existent_data.xml"> 103]> 104<document lang="en"> 105 <title>Sample Document</title> 106 <paragraph>&internal_greeting; This paragraph uses an internal entity.</paragraph> 107 <paragraph>This paragraph references an external entity: &my_external_data;.</paragraph> 108 <final_message>End of document.</final_message> 109</document> 110XML; 111 112// 定義した関数を実行し、XMLをパースします。 113parseXmlWithExternalEntityHandler($xmlData);
このサンプルコードは、PHPを用いてXMLドキュメントを解析する基本的な方法と、特に外部エンティティ参照の処理に焦点を当てています。xml_set_external_entity_ref_handler関数は、XMLドキュメント内で外部ファイルやURIが参照される際に、その処理を行うカスタムハンドラを設定するために使用されます。引数として、解析中のXMLパーサーオブジェクトと、外部エンティティが検出されたときに呼び出されるコールバック関数を指定します。このコールバック関数は、参照されたエンティティのシステムIDやパブリックIDといった情報を受け取り、PHPにデータの解決方法を指示する役割を持ちます。関数自体は常にtrueを返します。
サンプルでは、外部ファイルが存在しない場合でも、参照があるだけでこのハンドラが起動し、その詳細を確認できることが示されています。これにより、XMLのセキュリティ関連の取り扱いを学ぶ上で重要な概念となります。また、xml_set_element_handler関数を用いて要素の開始・終了を、xml_set_character_data_handler関数で要素間のテキストデータを処理する方法も同時に示されており、XMLパースの一連の流れを包括的に理解できます。これらのハンドラを設定後、xml_parseで実際の解析が実行され、パーサーは最終的にxml_parser_freeで解放されます。システムエンジニアを目指す初心者にとって、XMLデータの構造とイベント駆動型解析の基本を学ぶ良い出発点となるでしょう。
xml_set_external_entity_ref_handlerは、DTDで定義された外部エンティティが参照された際に呼び出されますが、参照されたデータの自動的な読み込みやパースは行いません。ハンドラ内で、ご自身で外部データの取得とxml_parse_external_entity()によるパース処理を実装する必要があります。外部エンティティ参照はXXE(XML External Entity)攻撃のリスクがあるため、セキュリティに十分注意が必要です。信頼できないソースからのXMLを処理する場合、外部エンティティの解決を無効にするか、厳格な検証を行うことが重要です。ハンドラ関数がtrueを返すとパース処理が継続されますが、falseを返すとエラーとして扱われます。この関数はext/xml拡張機能の一部であり、PHPはデフォルトで外部エンティティの読み込みを無効にしています。ハンドラを設定すると、開発者が明示的に外部リソースを扱うことになりますので、その責任とセキュリティへの配慮を忘れないでください。
xml_set_external_entity_ref_handler で外部実体参照を処理する
1<?php 2 3// 一時的な外部実体コンテンツファイル名 4$externalFileName = 'external_content.txt'; 5 6// 1. XMLパーサーを作成します 7$parser = xml_parser_create('UTF-8'); 8 9/** 10 * XMLパーサーの外部実体参照ハンドラ (クロージャ) 11 * XMLドキュメントがDTDで外部実体を参照している場合に呼び出されます。 12 * このハンドラで、外部実体をどのように解決するかを定義します。 13 * 14 * @param XMLParser $parser 現在のXMLパーサーインスタンス 15 * @param string $openEntityNames 現在開いている実体名のリスト (スペース区切り) 16 * @param string $base 外部実体参照が解決されるべきURIのベース 17 * @param string $systemId 外部実体のシステム識別子 (通常はファイルパスやURL) 18 * @param string $publicId 外部実体の公開識別子 19 * @return bool 実体を正常に処理できた場合は true、そうでない場合は false 20 */ 21$handleExternalEntity = function ( 22 XMLParser $parser, 23 string $openEntityNames, 24 string $base, 25 string $systemId, 26 string $publicId 27) use ($externalFileName): bool { 28 echo "--- 外部実体参照ハンドラが呼び出されました ---\n"; 29 echo " システムID: " . $systemId . "\n"; 30 echo " 公開ID: " . ($publicId ?: "(なし)") . "\n"; 31 32 // システムIDが、作成した一時ファイル名と一致するか確認 33 if ($systemId === $externalFileName) { 34 // 外部ファイルの内容を読み込む 35 $externalContent = file_get_contents($systemId); 36 if ($externalContent !== false) { 37 echo " 外部コンテンツ '{$systemId}' を読み込みました:\n"; 38 echo " " . trim($externalContent) . "\n"; 39 // ここで、読み込んだ外部コンテンツをXMLパーサーに統合する処理を実装できます。 40 // 例えば、xml_parser_create_external_entity_parser() を使用するなど。 41 // このサンプルでは、ハンドラが呼び出され、内容を読み込むところまでを示します。 42 return true; // 処理に成功したことを示す 43 } else { 44 echo " エラー: 外部コンテンツ '{$systemId}' の読み込みに失敗しました。\n"; 45 } 46 } else { 47 echo " 警告: 未知のシステムID '{$systemId}' が指定されました。\n"; 48 } 49 50 return false; // 処理に失敗したことを示す 51}; 52 53// 2. 外部実体参照ハンドラを設定します 54// callable 型の $handleExternalEntity クロージャをハンドラとして登録します。 55xml_set_external_entity_ref_handler($parser, $handleExternalEntity); 56 57// 3. 解析するXMLデータを準備します 58// DTDをインラインで定義し、「my_external_entity」という名前で外部ファイルをシステムIDとして参照します。 59// XMLドキュメント内の「&my_external_entity;」が参照されると、上記ハンドラが呼び出されます。 60$xmlData = <<<XML 61<!DOCTYPE root [ 62 <!ENTITY my_external_entity SYSTEM "{$externalFileName}"> 63]> 64<root> 65 <data>これは内部データです。</data> 66 <external_ref>&my_external_entity;</external_ref> 67</root> 68XML; 69 70// 4. 外部実体の内容を一時ファイルに書き込みます 71file_put_contents($externalFileName, "これは外部実体ファイルの内容です。"); 72 73echo "--- XMLパースを開始します ---\n"; 74 75// 5. XMLデータを解析します 76$result = xml_parse($parser, $xmlData); 77 78if (!$result) { 79 // パースエラーが発生した場合 80 echo "XMLパースエラー: " . xml_get_error_code($parser) . " - " . xml_error_string(xml_get_error_code($parser)) . "\n"; 81 echo " 行: " . xml_get_current_line_number($parser) . "\n"; 82} else { 83 echo "XMLパースが正常に完了しました。\n"; 84} 85 86// 6. XMLパーサーを解放します 87xml_parser_free($parser); 88 89// 7. 作成した一時ファイルを削除します 90unlink($externalFileName); 91 92echo "--- プログラムを終了します ---\n";
PHPのxml_set_external_entity_ref_handler関数は、XMLドキュメントを解析する際に、DTDなどで定義された外部実体参照が検出された場合のカスタム処理を設定するために使用されます。
第一引数$parserには、xml_parser_createで作成したXMLパーサーのインスタンスを指定します。第二引数$handlerには、外部実体参照が見つかったときに実行されるコールバック関数(クロージャや関数名を文字列で指定)を設定します。この関数は設定が成功するとtrueを返します。
サンプルコードでは、まずXMLパーサーを作成し、xml_set_external_entity_ref_handlerを使って、外部実体参照 &my_external_entity; が検出された際に呼び出されるハンドラ関数 $handleExternalEntity を登録しています。XML解析中に外部実体参照が見つかると、設定したハンドラが自動的に呼び出され、参照された外部ファイル(例: external_content.txt)のシステムID(ファイルパス)に基づいて、コンテンツを読み込み、出力する処理を実行します。これにより、XMLパーサーが外部リソースを参照する際の動作をプログラムで柔軟に制御し、外部コンテンツの読み込みや統合ロジックを実装できるようになります。
この関数は、XMLドキュメントが外部ファイルなどの実体を参照した際に、その解決方法を独自に定義するために利用されます。ハンドラ内で外部リソースを処理する際は、参照元の信頼性を必ず確認し、悪意のあるXMLからの攻撃(XXE攻撃など)に対するセキュリティ対策を十分に講じてください。ハンドラが外部実体の処理に成功した場合はtrue、失敗した場合はfalseを返します。サンプルコードのように外部ファイルを扱う場合、実運用では一時ファイルの適切な管理と、セキュリティを考慮したパスの検証が重要です。実際に外部実体をパースに含める場合は、xml_parser_create_external_entity_parser()関数との連携も検討してください。