【PHP8.x】DOMDocument::adoptNode()メソッドの使い方
adoptNodeメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
adoptNodeメソッドは、指定されたDOMノードの所有ドキュメントを、メソッドを呼び出したDOMDocumentインスタンスに変更するメソッドです。
このメソッドは、異なるDOMDocumentに属するノードを、現在のドキュメントで安全に利用可能にすることを目的とします。渡されたノードが別のドキュメントに属している場合、そのノードの所有ドキュメントを現在のドキュメントに移します。既に現在のドキュメントに属するノードは、所有権が変更されずそのまま返されます。
adoptNodeメソッドは、ノードの所有ドキュメントを変更するだけで、ノードのドキュメントツリー内の位置や親ノードからの削除には影響を与えません。ノードの追加には、appendChildやinsertBeforeなどのDOM操作メソッドを続けて使用します。
複数のXMLやHTMLドキュメントから要素を集約し、新しいドキュメントを構築するようなシナリオで特に有用です。異なるドキュメントから取得したノードを現在のドキュメントに組み込む際、この所有権の変更は、ノードが新しいドキュメントの一部として正しく機能するために不可欠となります。
構文(syntax)
1<?php 2$originalDocument = new DOMDocument(); 3$originalElement = $originalDocument->createElement('root', 'Original Content'); 4$originalDocument->appendChild($originalElement); 5 6$targetDocument = new DOMDocument(); 7$adoptedNode = $targetDocument->adoptNode($originalElement); 8 9if ($adoptedNode instanceof DOMNode) { 10 $targetDocument->appendChild($adoptedNode); 11 echo $targetDocument->saveXML(); 12} else { 13 echo "ノードの採用(adopt)に失敗しました。\n"; 14}
引数(parameters)
DOMNode $node
- DOMNode $node: 別のDOMDocumentから移動させたいDOMNodeオブジェクト
戻り値(return)
DOMNode|false
指定されたノードを現在のDOMDocumentの所有権下に移動させ、成功した場合は移動されたDOMNodeオブジェクトを、失敗した場合はfalseを返します。
サンプルコード
PHP DOMDocument::adoptNode を使ったノードの採用
1<?php 2 3/** 4 * DOMDocument::adoptNode メソッドのサンプルコード。 5 * 6 * このメソッドは、あるDOMDocumentで作成された、まだどのツリーにも追加されていないノード、 7 * あるいは別のDOMDocumentに属するノードを、現在のDOMDocumentに「養子縁組」させるために使用されます。 8 * ノードの所属ドキュメント(ownerDocument)を変更しますが、 9 * 実際にDOMツリーに追加するためには、appendChildなどのメソッドを別途呼び出す必要があります。 10 * 11 * この例では、まず `$dom1` でノードを作成し、それを `$dom2` に採用(adopt)し、 12 * 最終的に `$dom2` のDOMツリーに追加する流れを示します。 13 */ 14function demonstrateAdoptNodeForBeginners(): void 15{ 16 // 最初のDOMDocumentを作成 17 $dom1 = new DOMDocument('1.0', 'UTF-8'); 18 $dom1->formatOutput = true; // 出力を整形するために設定 19 // ルート要素を持つXMLをロード 20 $dom1->loadXML('<document1><root_element/></document1>'); 21 echo "--- ドキュメント1の初期状態 ---\n" . $dom1->saveXML() . "\n"; 22 23 // 2番目のDOMDocumentを作成 24 $dom2 = new DOMDocument('1.0', 'UTF-8'); 25 $dom2->formatOutput = true; // 出力を整形するために設定 26 // ルート要素とコンテナ要素を持つXMLをロード 27 $dom2->loadXML('<document2><container>Initial content</container></document2>'); 28 echo "--- ドキュメント2の初期状態 ---\n" . $dom2->saveXML() . "\n"; 29 30 // ドキュメント1で新しい要素ノードを作成します。 31 // このノードはまだどのDOMツリーにも追加されていません。 32 $newNode = $dom1->createElement('item', 'This is an item from DOM1'); 33 $newNode->setAttribute('id', 'new-item'); 34 echo "作成したノード: <" . $newNode->nodeName . "> (値: " . $newNode->nodeValue . ")\n"; 35 // ノードが現在どのDOMDocumentに属しているか(ownerDocument)を表示 36 echo "ノードが現在属するドキュメント (ownerDocument): " . spl_object_hash($newNode->ownerDocument) . "\n\n"; 37 38 // ドキュメント2にノードを採用 (adoptNode) します。 39 // adoptNodeはノードの ownerDocument を $dom2 に変更しますが、DOMツリーには追加しません。 40 // 戻り値は採用されたノード自身、または失敗時には false です。 41 $adoptedNode = $dom2->adoptNode($newNode); 42 43 if ($adoptedNode === false) { 44 echo "エラー: adoptNode に失敗しました。\n"; 45 return; 46 } 47 48 echo "ノード '{$adoptedNode->nodeName}' をドキュメント2に採用しました。\n"; 49 // 採用後のノードが属するDOMDocumentを表示。$dom2のハッシュと同じになるはずです。 50 echo "採用後のノードが属するドキュメント (ownerDocument): " . spl_object_hash($adoptedNode->ownerDocument) . "\n\n"; 51 52 // ドキュメント2の 'container' 要素を取得し、採用したノードを追加します。 53 // これでノードは実際にドキュメント2のDOMツリーの一部となります。 54 $container = $dom2->getElementsByTagName('container')->item(0); 55 if ($container instanceof DOMElement) { 56 $container->appendChild($adoptedNode); 57 echo "採用したノードをドキュメント2の 'container' に追加しました。\n\n"; 58 } else { 59 echo "エラー: ドキュメント2に 'container' 要素が見つかりませんでした。\n"; 60 return; 61 } 62 63 echo "--- adoptNode と appendChild 後、ドキュメント2の最終状態 ---\n"; 64 echo $dom2->saveXML() . "\n"; 65 66 echo "--- ドキュメント1の最終状態 ---\n"; 67 // ドキュメント1のDOMツリーは変更されていません。 68 echo $dom1->saveXML() . "\n"; 69} 70 71// サンプルコードを実行します 72demonstrateAdoptNodeForBeginners(); 73 74?>
PHP 8のDOMDocument::adoptNodeメソッドは、あるDOMドキュメントに属するノード、またはまだどのDOMツリーにも追加されていないノードを、メソッドを呼び出した現在のDOMドキュメントに「採用」するために使用されます。このメソッドの主な役割は、ノードの所属ドキュメント(ownerDocument)を変更することであり、実際にDOMツリーへノードを追加するものではありません。ツリーに追加するには、別途appendChildなどのメソッドを呼び出す必要があります。
引数には、現在のドキュメントに採用したいDOMNodeオブジェクトを指定します。このノードの所属情報が、メソッドを呼び出したDOMDocumentオブジェクトに変更されます。
戻り値は、採用に成功した場合は引数で渡したDOMNodeオブジェクト自体を返します。何らかの理由で採用に失敗した場合はfalseを返しますので、エラーハンドリングを行うことが重要です。
サンプルコードでは、まず二つの異なるDOMドキュメント $dom1 と $dom2 を準備しています。次に $dom1 上で新たな要素ノード $newNode を作成していますが、このノードはまだ $dom1 のDOMツリーには追加されていません。その後 $dom2->adoptNode($newNode) を実行することで、$newNode の所属が $dom1 から $dom2 へと変更されます。これにより $newNode は $dom2 のノードとして扱えるようになります。最後に $dom2 の既存要素である container に appendChild メソッドを用いて $adoptedNode を追加することで、初めてノードが $dom2 のDOMツリーに組み込まれるという一連の流れを示しています。これにより、異なるドキュメント間でノードを安全に移動させ、利用できる仕組みを理解することができます。
DOMDocument::adoptNodeは、ノードが属するドキュメントを変更する機能を持つメソッドです。このメソッドを呼び出すだけでは、ノードはDOMツリーには追加されません。ノードを実際のDOMツリーに追加するには、appendChildなどのメソッドを別途利用する必要があります。
また、メソッドの戻り値は、ノードの採用に成功した場合は採用されたノード自身、失敗した場合はfalseとなります。そのため、処理を進める前に必ず戻り値を確認し、適切にエラーハンドリングを行うようにしてください。この操作は、元のドキュメントのツリー構造には影響を与えません。異なるDOMDocument間でノードを移動させる際に重要な役割を果たします。
PHP DOMDocument::adoptNodeでXML生成
1<?php 2 3/** 4 * ADODBのモックアップ 5 * 6 * 実際にはComposerで 'adodb/adodb-php' をインストールし、 7 * データベースへの実際の接続情報を使用してADODBを初期化します。 8 * ここでは、サンプルコードが単体で動作するようにADODBの振る舞いを模倣しています。 9 */ 10 11// ADODBのレコードセットを模倣するクラス 12class MockADORecordSet 13{ 14 private array $data; 15 private int $pointer = 0; 16 17 public function __construct(array $data) 18 { 19 $this->data = $data; 20 } 21 22 /** 23 * 次の行を取得します。 24 * 実際のADODBは設定により連想配列や数値配列を返しますが、ここでは連想配列を返します。 25 */ 26 public function FetchRow(): ?array 27 { 28 if ($this->pointer < count($this->data)) { 29 return $this->data[$this->pointer++]; 30 } 31 return null; 32 } 33 34 /** 35 * レコードセットの終端に達したかを確認します。 36 */ 37 public function EOF(): bool 38 { 39 return $this->pointer >= count($this->data); 40 } 41} 42 43// ADODBの接続オブジェクトを模倣するクラス 44class MockADODBConnection 45{ 46 /** 47 * SQLクエリを実行し、モックレコードセットを返します。 48 */ 49 public function Execute(string $sql): MockADORecordSet 50 { 51 // SQLの内容によらず、ここでは固定の商品データを返します。 52 // description_htmlには、DOMDocumentでパース可能なHTML文字列を想定しています。 53 $mockData = [ 54 [ 55 'id' => 1, 56 'name' => 'PHP入門書', 57 'price' => 2800, 58 'description_html' => '<p>PHP 8に<strong>対応</strong>した入門書です。</p><p>Web開発の基礎が学べます。</p>' 59 ], 60 [ 61 'id' => 2, 62 'name' => 'MySQLリファレンス', 63 'price' => 3500, 64 'description_html' => '<p>MySQLの<em>詳細な機能</em>と最適化について解説しています。</p>' 65 ], 66 ]; 67 return new MockADORecordSet($mockData); 68 } 69} 70 71/** 72 * ADODBのADONewConnection()関数を模倣します。 73 * 実際には、データベースの種類と接続情報に基づいてADODBオブジェクトを生成します。 74 */ 75function ADONewConnection(string $dsn): MockADODBConnection 76{ 77 // DSN(Data Source Name)は実際の接続情報の一部ですが、モックでは利用しません。 78 return new MockADODBConnection(); 79} 80 81/** 82 * ADODBから取得したデータとDOMDocument::adoptNodeを使用してXMLを生成するサンプル関数 83 * 84 * システムエンジニアを目指す初心者向けに、DOMDocument::adoptNodeの役割と 85 * ADODBで取得したデータをXMLに変換する基本的な流れを示します。 86 * 87 * adoptNodeは、あるDOMDocumentに属するノードを別のDOMDocumentに「養子に迎える」ように移動させるメソッドです。 88 * これにより、異なるXML/HTMLフラグメントを一つのドキュメントに安全に統合できます。 89 * 例えば、データベースにHTMLとして保存された内容を、XMLドキュメントの一部として組み込む際に役立ちます。 90 * 91 * @return string 生成されたXML文字列、またはエラーメッセージ 92 */ 93function generateProductXmlWithAdoptNode(): string 94{ 95 // 1. ADODB接続の準備 (モックアップを使用) 96 // 実際には以下のようにデータベースに接続します: 97 // $db = ADONewConnection('mysql://user:password@host/database_name'); 98 // $db->SetFetchMode(ADODB_FETCH_ASSOC); // 結果を連想配列で取得する設定 99 $db = ADONewConnection('mock_dsn_for_demo'); 100 101 // 2. メインのXMLドキュメントを作成 102 $mainDoc = new DOMDocument('1.0', 'UTF-8'); 103 $mainDoc->formatOutput = true; // 出力を整形して見やすくする設定 104 $root = $mainDoc->createElement('products'); 105 $mainDoc->appendChild($root); 106 107 try { 108 // 3. ADODBで商品データを取得 (モックアップを使用) 109 // 実際にはSQLクエリを記述します: 110 // $rs = $db->Execute('SELECT id, name, price, description_html FROM products_table'); 111 $rs = $db->Execute('SELECT * FROM products_mock_data'); 112 113 // 4. 取得したレコードセットをループしてXMLノードを作成 114 while ($row = $rs->FetchRow()) { 115 $productElement = $mainDoc->createElement('product'); 116 $root->appendChild($productElement); 117 118 $productElement->setAttribute('id', $row['id']); 119 120 $nameElement = $mainDoc->createElement('name', $row['name']); 121 $productElement->appendChild($nameElement); 122 123 $priceElement = $mainDoc->createElement('price', (string)$row['price']); 124 $productElement->appendChild($priceElement); 125 126 // 5. ここでDOMDocument::adoptNodeの出番! 127 // description_html(HTML文字列)を一時的なDOMDocumentでパースし、 128 // そのノードをmainDocの<description>要素に統合します。 129 $tempDoc = new DOMDocument('1.0', 'UTF-8'); 130 // loadHTMLはHTML文字列をパースし、自動的に<html><body>などのタグを追加します。 131 // 不完全なHTMLフラグメントをロードすると警告が出ることがあるため、@で抑制します。 132 @$tempDoc->loadHTML($row['description_html']); 133 134 $descriptionElement = $mainDoc->createElement('description'); 135 $productElement->appendChild($descriptionElement); 136 137 // loadHTMLによって作成された<body>要素の子ノードを取得します。 138 // loadHTMLは常に<html><body>...</body></html>構造を作成するため、<body>要素を探します。 139 $body = $tempDoc->getElementsByTagName('body')->item(0); 140 141 if ($body) { 142 // DOMのchildNodesプロパティは「ライブリスト」です。 143 // リストをイテレート中にノードがadoptNodeによって移動(削除)されると、 144 // リストの構造が変化し、予期せぬ挙動になる可能性があります。 145 // そのため、先にノードを配列にコピーしてから処理するのが安全です。 146 $nodesToAdopt = []; 147 foreach ($body->childNodes as $childNodeFromTempDoc) { 148 $nodesToAdopt[] = $childNodeFromTempDoc; 149 } 150 151 foreach ($nodesToAdopt as $childNodeFromTempDoc) { 152 // adoptNodeメソッドは、別のDOMDocumentに属するノードを 153 // 現在のDOMDocument($mainDoc)に「養子に迎えます」。 154 // これにより、ノードの所有ドキュメントが$mainDocに変わり、 155 // 元のドキュメント($tempDoc)からはそのノードが削除されます。 156 $adoptedNode = $mainDoc->adoptNode($childNodeFromTempDoc); 157 158 if ($adoptedNode) { 159 // 養子に迎えたノードを$mainDoc内の適切な場所(ここでは<description>要素)に追加します。 160 $descriptionElement->appendChild($adoptedNode); 161 } 162 } 163 } 164 } 165 } catch (Throwable $e) { 166 // エラーハンドリング 167 error_log('ADODBまたはDOM操作でエラーが発生しました: ' . $e->getMessage()); 168 return "エラーが発生しました: " . $e->getMessage(); 169 } 170 171 // 6. 生成されたXMLを文字列として出力 172 return $mainDoc->saveXML(); 173} 174 175// サンプル関数の実行と結果の表示 176echo generateProductXmlWithAdoptNode();
PHPのDOMDocument::adoptNodeメソッドは、あるXML(またはHTML)ドキュメントに属するノードを、別のDOMDocumentオブジェクトへ安全に移動させるために使用されます。引数DOMNode $nodeには移動させたいノードを指定し、成功すれば新しいドキュメントに属するそのノードを、失敗すればfalseを返します。
このサンプルコードでは、ADODBのモックアップを使用してデータベースから商品データを取得し、それをXML形式で出力するプロセスを示しています。特に、データベースにHTML形式で保存されている商品説明文(description_html)を、メインのXMLドキュメントに統合する際にadoptNodeメソッドが活躍します。
具体的には、取得したHTML文字列を一時的なDOMDocumentで解析し、その解析結果から得られた子ノードを、メインのXMLドキュメント($mainDoc)の<description>要素へ移動させています。adoptNodeを使うことで、異なるドキュメントに由来するノードの所有権を安全にメインドキュメントへ移すことができ、HTMLフラグメントをXMLの一部として適切に組み込むことが可能になります。ノードが採用されると、元のドキュメントからは自動的に削除され、新しいドキュメントの一部となります。これにより、データソースが異なる情報も一つの統一されたXML構造として管理できる利点があります。
このサンプルコードにおけるADODBはモックアップであり、実際のシステム開発ではADODBライブラリの導入と、適切なデータベース接続設定が別途必要です。DOMDocument::adoptNodeは、異なるDOMDocumentに属するノードを現在のドキュメントへ安全に移動させるメソッドで、移動元のノードは元のドキュメントから削除される点に注意してください。HTML文字列をloadHTMLでパースすると自動的に<html><body>タグが追加されるため、目的のノードを取得する際はその構造を考慮することが大切です。また、ノードリストをループ中にadoptNodeでノードを移動させると、リストの構造が変化して予期せぬ動作を招く可能性があります。そのため、サンプルコードのように移動対象のノードを事前に別の配列にコピーしてから処理する手法が安全です。