【PHP8.x】Dom\EntityReference::C14N()メソッドの使い方
C14Nメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『C14Nメソッドは、エンティティ参照ノードおよびその配下にあるすべての子孫ノードを、正規化された形式の文字列に変換して出力するメソッドです。XML文書は、記述方法が異なっていても論理的に同じ内容を表すことがあります。例えば、属性の順序や空白の有無が違っても意味は同じです。正規化(Canonicalization)とは、このような表記の揺れを統一されたルールに従って整形し、意味的に等価な文書が常に一意の文字列表現になるように変換する処理を指します。これにより、デジタル署名での検証や、二つの文書が内容的に同一であるかを正確に比較することが可能になります。このメソッドは、コメントを含めるか否かを指定する with_comments や、排他的正規化を行うかを指定する exclusive といった引数を通じて、W3Cの仕様に基づいた正規化の挙動を詳細に制御できます。処理が成功すると正規化された文字列を返し、失敗した場合は false を返します。
構文(syntax)
1<?php 2 3$doc = new Dom\Document(); 4$doc->loadXML('<!DOCTYPE r [<!ENTITY e "entity content">]><r>&e;</r>'); 5 6$entityRef = $doc->documentElement->firstChild; 7 8$canonicalString = $entityRef->C14N( 9 xpath_query: null, 10 exclusive: false, 11 with_comments: false, 12 ns_prefixes: null 13); 14
引数(parameters)
bool $exclusive = false, bool $withComments = false, ?array $xpath = null, ?array $nsPrefixes = null
- bool $exclusive: true の場合、排他的な正規化を有効にします。
- bool $withComments: true の場合、コメントノードも正規化に含めます。
- ?array $xpath: 正規化の対象となるノードを XPath 式の配列で指定します。
- ?array $nsPrefixes: 名前空間プレフィックスと URI のマッピング配列を指定します。
戻り値(return)
string|false
このメソッドは、XMLエンティティ参照を正規化された文字列で返します。正規化に失敗した場合はfalseを返します。
サンプルコード
PHP Dom\EntityReference C14N正規化
1<?php 2 3/** 4 * XMLエンティティ参照ノードのC14N正規化のデモンストレーション。 5 * Dom\EntityReference クラスのインスタンス (実際には親クラス DOMNode から継承) で C14N メソッドを使用する方法を、 6 * システムエンジニアを目指す初心者にも理解できるように示します。 7 */ 8function demonstrateDomEntityReferenceC14N(): void 9{ 10 // C14N正規化の対象となるXML文字列を定義します。 11 // エンティティ参照ノード (例: &myEntity;) をDOMツリーに保持するため、 12 // 内部DTDでエンティティを定義し、特別なロードオプションを使用します。 13 $xmlString = <<<XML 14<?xml version="1.0" encoding="UTF-8"?> 15<!DOCTYPE root [ 16 <!ENTITY myEntity "entity_value"> 17 <!-- これはDTD内のコメントです。 --> 18]> 19<root> 20 <item> 21 テキストと&myEntity;の組み合わせ。 22 <!-- これは要素内のコメントです。 --> 23 </item> 24</root> 25XML; 26 27 // DOMDocumentのインスタンスを作成します。 28 $dom = new DOMDocument(); 29 // 出力を整形するための設定 (C14N自体には影響しません) 30 $dom->preserveWhiteSpace = false; 31 $dom->formatOutput = true; 32 33 // XMLをロードします。 34 // LIBXML_NOENT: エンティティ参照を展開せずに、DOMEntityReferenceノードとしてDOMツリーに残します。 35 // LIBXML_DTDLOAD: 外部DTD、または内部DTD(この例のように)をロードし、エンティティ定義を認識できるようにします。 36 if (!$dom->loadXML($xmlString, LIBXML_NOENT | LIBXML_DTDLOAD)) { 37 echo "エラー: XMLのロードに失敗しました。\n"; 38 return; 39 } 40 41 // DOMツリーから Dom\EntityReference ノードを見つけます。 42 // DOMEntityReference は Dom\EntityReference のエイリアスです。 43 $entityReferenceNode = null; 44 foreach ($dom->getElementsByTagName('item') as $itemElement) { 45 foreach ($itemElement->childNodes as $childNode) { 46 if ($childNode instanceof DOMEntityReference) { 47 $entityReferenceNode = $childNode; 48 break 2; // エンティティ参照ノードが見つかったら、すべてのループを終了 49 } 50 } 51 } 52 53 if ($entityReferenceNode === null) { 54 echo "エラー: XML内にDom\\EntityReferenceノードが見つかりませんでした。\n"; 55 echo "エンティティ参照が正しく定義され、XMLロード時にLIBXML_NOENTフラグが使用されているか確認してください。\n"; 56 return; 57 } 58 59 echo "--- 検出されたDom\\EntityReferenceノードの情報 ---\n"; 60 // 検出されたエンティティ参照ノードの、展開された値 (nodeValue) 61 echo "nodeValue (参照先の値): '" . $entityReferenceNode->nodeValue . "'\n"; 62 // 検出されたエンティティ参照ノードの名前 (エンティティ名) 63 echo "nodeName (エンティティ名): '" . $entityReferenceNode->nodeName . "'\n\n"; 64 65 // --- C14N正規化の実行 --- 66 // C14Nメソッドは DOMNode クラス (Dom\EntityReference の親クラス) に定義されています。 67 // そのため、Dom\EntityReference のインスタンスでもこのメソッドを呼び出すことができます。 68 69 // Case 1: デフォルトのC14N正規化 (排他的正規化なし、コメントなし) 70 // 引数を指定しない場合、または $exclusive=false, $withComments=false と指定した場合の動作です。 71 // Dom\EntityReference ノードの場合、通常はエンティティ参照そのもの (例: &myEntity;) の形式が結果となります。 72 // nodeValue ('entity_value') とは異なる点に注意してください。 73 $canonicalDefault = $entityReferenceNode->C14N(); 74 if ($canonicalDefault !== false) { 75 echo "--- Case 1: デフォルトのC14N正規化結果 (exclusive=false, withComments=false) ---\n"; 76 echo "正規化されたエンティティ参照: " . $canonicalDefault . "\n\n"; 77 } else { 78 echo "エラー: デフォルトのC14N正規化に失敗しました。\n\n"; 79 } 80 81 // Case 2: コメントを含めてC14N正規化 (排他的正規化なし、コメントあり) 82 // 第二引数 $withComments を true に設定することで、C14N処理がコメントを含めるように試みます。 83 // ただし、この Dom\EntityReference ノード自体やその子孫には直接コメントノードがないため、 84 // このケースでは Case 1 と同じ結果になります。 85 $canonicalWithComments = $entityReferenceNode->C14N(false, true); 86 if ($canonicalWithComments !== false) { 87 echo "--- Case 2: コメントを含むC14N正規化結果 (exclusive=false, withComments=true) ---\n"; 88 echo "正規化されたエンティティ参照: " . $canonicalWithComments . "\n\n"; 89 } else { 90 echo "エラー: コメントを含むC14N正規化に失敗しました。\n\n"; 91 } 92} 93 94// サンプル関数を実行します 95demonstrateDomEntityReferenceC14N();
PHPのDom\EntityReference::C14Nメソッドは、XML文書内のエンティティ参照ノード(&myEntity;のような形式)を、XMLの標準的な正規化形式(Canonical XML)の文字列に変換する機能を提供します。この正規化は、XML文書の見た目や論理的な内容は同じでも、バイト列が異なる場合に、それらを統一された表現に変換するために使用され、特にXMLの電子署名などで文書の同一性を保証する際に重要となります。
Dom\EntityReferenceは、DOMツリー内でエンティティ参照を表すノードであり、このメソッドはその親クラスであるDOMNodeから継承されています。エンティティ参照ノードに対してC14Nメソッドを呼び出すと、通常はエンティティの実体ではなく、参照形式そのもの(例:&myEntity;)が正規化された文字列として返されます。
引数$exclusiveは排他的正規化を行うか否か(デフォルトはfalse)、$withCommentsは正規化結果にコメントを含めるか否か(デフォルトはfalse)を指定します。ただし、Dom\EntityReferenceノード自体には通常コメントが含まれないため、$withCommentsをtrueにしても結果が変わらない場合があります。$xpathと$nsPrefixesは、正規化の対象となるノードの範囲をXPathで指定する際に使用しますが、省略可能です。
このメソッドは、成功すれば正規化されたXML文字列を返しますが、失敗した場合はfalseを返します。エンティティ参照をDOMツリーに保持するためには、DOMDocument::loadXMLやDOMDocument::loadの際にLIBXML_NOENTおよびLIBXML_DTDLOADフラグを指定する必要があります。これにより、XML文書中のエンティティ参照を、その実体に展開せずにDom\EntityReferenceオブジェクトとして扱うことが可能になります。
このC14NメソッドはXMLエンティティ参照ノードを正規化する際に利用されます。特に注意が必要なのは、サンプルコードのようにDOMDocument::loadXML()でLIBXML_NOENTフラグを指定しないと、XML内のエンティティが展開されてしまい、Dom\EntityReferenceノードがDOMツリーに存在しなくなり、メソッドを呼び出す対象が見つからない点です。
C14Nメソッドは、エンティティが参照されている形式(例:&myEntity;)を正規化するため、エンティティが実際に展開された値(nodeValue)とは異なる結果が返されることが多いです。この違いを理解することが重要です。
また、メソッドは成功時に正規化された文字列を返しますが、失敗時にはfalseを返します。必ず戻り値を確認し、適切にエラー処理を行うことで、予期せぬ動作を防ぎ、安全なコードを記述してください。引数の$withCommentsは、エンティティ参照ノード自体にコメントがない限り結果に影響しません。
PHP Dom\EntityReference::C14N でXMLエンティティ正規化する
1<?php 2 3/** 4 * Dom\EntityReference::C14N の使用例を示します。 5 * 6 * この関数は、DTD(文書型定義)で定義されたエンティティ参照を含むXMLを作成し、 7 * そのエンティティ参照ノードを正規化(C14N)して結果を出力します。 8 * 正規化とは、XMLの論理的な意味を変えずに、物理的な表現(バイト列)を 9 * 一意に定めるプロセスで、電子署名などで利用されます。 10 */ 11function demonstrateEntityReferenceC14N(): void 12{ 13 // 1. DTDとエンティティ参照を含むXML文字列を定義します。 14 // DTD内でエンティティ 'writer' を 'Sample Author' として定義し、 15 // <author> タグ内で '&writer;' として参照しています。 16 $xmlString = <<<XML 17<?xml version="1.0" encoding="UTF-8"?> 18<!DOCTYPE root [ 19 <!ENTITY writer "Sample Author"> 20]> 21<root> 22 <author>&writer;</author> 23</root> 24XML; 25 26 // 2. DOMDocumentオブジェクトを作成し、XMLを読み込みます。 27 $doc = new DOMDocument(); 28 // 外部エンティティの読み込みを無効化しセキュリティを確保します(ベストプラクティス)。 29 if (!$doc->loadXML($xmlString, LIBXML_NONET)) { 30 echo "XMLの読み込みに失敗しました。\n"; 31 return; 32 } 33 34 // 3. XPathを使用してエンティティ参照ノードを取得します。 35 // <author> タグの最初の子ノードがエンティティ参照ノードに該当します。 36 $xpath = new DOMXPath($doc); 37 $nodeList = $xpath->query('//author/node()'); 38 39 if ($nodeList === false || $nodeList->length === 0) { 40 echo "ノードが見つかりませんでした。\n"; 41 return; 42 } 43 44 $entityRefNode = $nodeList->item(0); 45 46 // 4. ノードが Dom\EntityReference のインスタンスであることを確認し、C14N() を実行します。 47 if ($entityRefNode instanceof Dom\EntityReference) { 48 // C14N() メソッドを呼び出し、ノードを正規化します。 49 // エンティティ参照ノードの場合、その中身(展開されたテキスト)が返されます。 50 $canonicalString = $entityRefNode->C14N(); 51 52 // 5. 結果を出力します。 53 echo "エンティティ参照ノード '&" . $entityRefNode->nodeName . ";' の正規化結果:\n"; 54 echo "--------------------------------------------------\n"; 55 echo $canonicalString . "\n"; 56 } else { 57 echo "エンティティ参照ノードが見つかりませんでした。\n"; 58 } 59} 60 61// 関数を実行します。 62demonstrateEntityReferenceC14N(); 63
PHPの Dom\EntityReference::C14N() メソッドは、XML文書内のエンティティ参照ノードを正規化し、その結果を文字列として取得します。正規化(C14N)とは、XMLの論理的な意味を保ったまま、その物理的なバイト表現を標準的な形式に統一する処理です。これにより、XMLデータがバイト単位で比較可能となり、電子署名の検証などで利用されます。
サンプルコードでは、まずDTD(文書型定義)で &writer; というエンティティ参照を定義したXML文字列を準備します。次に、このXMLを読み込んで &writer; にあたるエンティティ参照ノードを取得し、C14N() メソッドを呼び出しています。このメソッドが実行されると、エンティティ参照はその定義内容であるテキスト「Sample Author」に置き換えられ、正規化された文字列として返されます。
C14N() メソッドには、排他的正規化の指定やコメントを含めるかどうかの設定など、正規化のルールを制御するための引数がありますが、これらは省略可能です。メソッドの戻り値は、処理が成功した場合は正規化された文字列 (string)、失敗した場合は false となります。
Dom\EntityReference::C14N()メソッドは、XMLのエンティティ参照ノードを正規化し、その中身を展開した文字列を返します。サンプルでは&writer;からSample Authorというテキストを取得しています。XMLを扱う際、特に外部のデータを読み込む場合は、loadXML関数の第二引数にLIBXML_NONETを指定することが非常に重要です。これにより、意図しない外部エンティティの読み込みを防ぎ、XXE脆弱性というセキュリティリスクを回避できます。また、XPathの//author/node()は<author>タグ直下の子ノードを取得する指定です。このコードのようにエンティティ参照が子ノードになっていることを確認してからC14N()を実行してください。正規化は電子署名などでXML文書の一意性を保証するための処理ですが、エンティティ参照ノードに使うと、定義されたテキストを取得できると理解すると分かりやすいです。