Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】Dom\Document::compareDocumentPosition()メソッドの使い方

compareDocumentPositionメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

compareDocumentPositionメソッドは、あるノードと引数で指定された別のノードの、ドキュメント内での位置関係を比較するために実行するメソッドです。このメソッドは、比較対象となるノードオブジェクトを引数として受け取り、2つのノードの位置関係を示す整数値を返します。この戻り値はビットマスクであり、複数の状態を同時に表現することが可能です。例えば、引数のノードが呼び出し元のノードよりも前に出現する場合は DOM_DOCUMENT_POSITION_PRECEDING が、後に出現する場合は DOM_DOCUMENT_POSITION_FOLLOWING が含まれます。また、引数のノードが呼び出し元のノードの子孫である場合は DOM_DOCUMENT_POSITION_CONTAINS が、逆に祖先である場合は DOM_DOCUMENT_POSITION_CONTAINED_BY が含まれます。返された値とこれらの定義済み定数をビット単位のAND演算子(&)で比較することで、具体的な位置関係を判定できます。この機能により、DOMツリー内における2つの要素の前後関係や親子関係を正確に把握することができ、複雑なDOM操作を行う際に非常に役立ちます。

構文(syntax)

1<?php
2
3$document = new Dom\Document();
4$document->loadXML('<root><nodeA></nodeA><nodeB></nodeB></root>');
5
6$nodeA = $document->getElementsByTagName('nodeA')->item(0);
7$nodeB = $document->getElementsByTagName('nodeB')->item(0);
8
9$position = $nodeA->compareDocumentPosition($nodeB);
10
11var_dump($position);
12
13?>

引数(parameters)

Dom\Node $other

  • Dom\Node $other: 比較対象のDOMノード

戻り値(return)

int

このメソッドは、2つのDOMノードの位置関係を示す整数値を返します。返される値は、ビットフラグとして扱われ、ノードが互いにどのような関係にあるか(例: 左側にある、右側にある、親である、子であるなど)を表します。

サンプルコード

PHP 8 DOMノード位置比較入門

1<?php
2
3namespace App\DomUtil;
4
5use Dom\Document;
6use Dom\Node;
7
8/**
9 * Dom\Document::compareDocumentPosition メソッドの使用例を示すクラス。
10 *
11 * このクラスは、DOMツリーにおける2つのノードの位置関係を比較する方法を提供します。
12 * 主にPHP 8で導入された新しいDom拡張を利用しており、
13 * Composerを使ったモダンなPHPプロジェクトでの利用を想定しています。
14 */
15class DomPositionComparer
16{
17    /**
18     * 2つのDOMノード間の位置関係を比較し、その結果を人間が読める形式で返します。
19     *
20     * compareDocumentPosition メソッドは、XML/HTML文書内のノードの相対的な位置を決定します。
21     * 戻り値はビットマスクの組み合わせであり、複数の関係を同時に示すことがあります。
22     *
23     * @param Node $nodeA 比較する最初のノード。
24     * @param Node $nodeB 比較する2番目のノード。
25     * @return string 2つのノード間の位置関係を説明する文字列。
26     */
27    public function describeNodePosition(Node $nodeA, Node $nodeB): string
28    {
29        // Dom\Document::compareDocumentPosition は Dom\Node クラスにも存在するメソッドです。
30        // ここでは便宜上 $nodeA から呼び出します。
31        $position = $nodeA->compareDocumentPosition($nodeB);
32
33        // 結果の整数値は、ビットマスクの組み合わせで表現されます。
34        // 各ビットは特定の位置関係を示し、Dom\Node クラスで定数として定義されています。
35        $descriptions = [];
36
37        // DOCUMENT_POSITION_SAME_NODE (0x00) は、他の定数とは異なり、
38        // ノードが完全に同一である場合にのみ返されるため、=== で厳密に比較します。
39        if ($position === Node::DOCUMENT_POSITION_SAME_NODE) {
40            $descriptions[] = '両方のノードは同じです。';
41        } else {
42            // ノードが互いに接続されていない(異なるドキュメントに属しているか、DOMツリーにまだ追加されていない)
43            if (($position & Node::DOCUMENT_POSITION_DISCONNECTED) === Node::DOCUMENT_POSITION_DISCONNECTED) {
44                $descriptions[] = '両方のノードは互いに接続されていません(異なるDOMツリーに属している可能性があります)。';
45            }
46
47            // $nodeA が $nodeB を含んでいる($nodeB が $nodeA の子孫)
48            if (($position & Node::DOCUMENT_POSITION_CONTAINS) === Node::DOCUMENT_POSITION_CONTAINS) {
49                $descriptions[] = '最初のノードが2番目のノードを含んでいます。';
50            }
51            // $nodeA が $nodeB に含まれている($nodeA が $nodeB の子孫)
52            if (($position & Node::DOCUMENT_POSITION_CONTAINED_BY) === Node::DOCUMENT_POSITION_CONTAINED_BY) {
53                $descriptions[] = '最初のノードは2番目のノードに含まれています。';
54            }
55
56            // $nodeA が $nodeB よりも文書順で前に位置する
57            if (($position & Node::DOCUMENT_POSITION_PRECEDING) === Node::DOCUMENT_POSITION_PRECEDING) {
58                $descriptions[] = '最初のノードは2番目のノードよりも文書順で前にあります。';
59            }
60            // $nodeA が $nodeB よりも文書順で後に位置する
61            if (($position & Node::DOCUMENT_POSITION_FOLLOWING) === Node::DOCUMENT_POSITION_FOLLOWING) {
62                $descriptions[] = '最初のノードは2番目のノードよりも文書順で後にあります。';
63            }
64        }
65
66        return implode(' ', $descriptions);
67    }
68}
69
70// ----- サンプルコードの実行部分 -----
71// このセクションは、クラスが単体で動作し、結果を表示するためのものです。
72// 実際のWebアプリケーションでは、フレームワークやルーティングを通じてクラスが利用されます。
73if (php_sapi_name() === 'cli' || !isset($_SERVER['HTTP_USER_AGENT'])) {
74    echo "Dom\\Document::compareDocumentPosition メソッドのサンプル実行:\n";
75    echo "---------------------------------------------------------\n\n";
76
77    // 比較に使用するHTMLドキュメントを作成します。
78    $html = <<<HTML
79<!DOCTYPE html>
80<html>
81<head>
82    <title>PHP DOM Position Sample</title>
83</head>
84<body>
85    <div id="container">
86        <p id="first-paragraph">これは最初の段落です。</p>
87        <p id="second-paragraph">これは2番目の段落です。</p>
88    </div>
89    <span id="standalone-span">これは独立した要素です。</span>
90</body>
91</html>
92HTML;
93
94    $document = new Document();
95    $document->loadHTML($html);
96
97    $comparer = new DomPositionComparer();
98
99    // 1. 同じノード同士の比較
100    $nodeA = $document->getElementById('first-paragraph');
101    $nodeB = $document->getElementById('first-paragraph');
102    echo "比較: '#first-paragraph' と '#first-paragraph'\n";
103    echo "結果: " . $comparer->describeNodePosition($nodeA, $nodeB) . "\n\n";
104
105    // 2. 親ノードと子ノードの比較(container と first-paragraph)
106    $nodeA = $document->getElementById('container');
107    $nodeB = $document->getElementById('first-paragraph');
108    echo "比較: '#container' と '#first-paragraph'\n";
109    echo "結果: " . $comparer->describeNodePosition($nodeA, $nodeB) . "\n\n";
110
111    // 3. 子ノードと親ノードの比較(first-paragraph と container)
112    $nodeA = $document->getElementById('first-paragraph');
113    $nodeB = $document->getElementById('container');
114    echo "比較: '#first-paragraph' と '#container'\n";
115    echo "結果: " . $comparer->describeNodePosition($nodeA, $nodeB) . "\n\n";
116
117    // 4. 兄弟ノードの比較(first-paragraph と second-paragraph)
118    $nodeA = $document->getElementById('first-paragraph');
119    $nodeB = $document->getElementById('second-paragraph');
120    echo "比較: '#first-paragraph' と '#second-paragraph'\n";
121    echo "結果: " . $comparer->describeNodePosition($nodeA, $nodeB) . "\n\n";
122
123    // 5. 異なるレベルにあるノードの比較(second-paragraph と standalone-span)
124    $nodeA = $document->getElementById('second-paragraph');
125    $nodeB = $document->getElementById('standalone-span');
126    echo "比較: '#second-paragraph' と '#standalone-span'\n";
127    echo "結果: " . $comparer->describeNodePosition($nodeA, $nodeB) . "\n\n";
128
129    // 注意: Dom\Node::compareDocumentPosition は、ノードが属するドキュメントが異なる場合、
130    // DOCUMENT_POSITION_DISCONNECTED フラグを設定します。
131    // このサンプルでは単一のドキュメントを使用しているため、
132    // 明示的に異なるドキュメントの比較は行っていません。
133}

PHP 8の新しいDOM拡張におけるDom\Document::compareDocumentPositionメソッドは、XMLやHTMLなどのDOMツリー上に存在する2つのノード間の相対的な位置関係を比較するために使用されます。このメソッドは、比較対象となるDom\Node型のオブジェクトを引数として受け取ります。戻り値は整数型で、これはビットマスクの組み合わせとして、両ノードが同一であるか、互いに含まれているか、文書順で前後に位置するか、あるいは全く接続されていないかといった複数の状態を同時に表現します。

提供されたサンプルコードは、このcompareDocumentPositionメソッドの利用方法を具体的に示しています。メソッドが返すビットマスク値を、Dom\Nodeクラスで定義されているさまざまな定数(例:DOCUMENT_POSITION_SAME_NODEDOCUMENT_POSITION_CONTAINSなど)とビット演算を使って比較することで、ノード間の位置関係を人間が理解しやすい説明文に変換するロジックが実装されています。これにより、システムエンジニアを目指す初心者は、複雑なDOM構造内でノードがどのように配置されているかを正確に判断する方法を学ぶことができます。このコードはComposerを使ったモダンなPHPプロジェクトで利用され、phpdocumentorによる適切なドキュメント化も施されており、実践的なDOM操作の理解に適しています。

PHP 8のDom拡張は名前空間がDom\に変更されました。compareDocumentPositionメソッドは、通常Dom\Nodeインスタンスから呼び出します。戻り値は複数の状態を示すビットマスクの整数です。各状態は&演算子でDom\Nodeの定数と比較して判定します。DOCUMENT_POSITION_SAME_NODEはノードが完全に同一の場合に===で厳密に比較します。ノードが異なるドキュメントに属する場合はDOCUMENT_POSITION_DISCONNECTEDフラグが立ちますので、異なるドキュメント間の比較にも注意が必要です。

PHP DomDocumentノード位置比較

1<?php
2
3/**
4 * Dom\Node::compareDocumentPosition メソッドによって返されるビットマスクを、
5 * 人間が読める形式の文字列配列に変換します。
6 *
7 * この関数は、指定されたビットマスクを解析し、それぞれのビットが示すDOMノード間の
8 * 位置関係を説明する文字列を生成します。これは、phpDocumentor が解析する
9 * PHPDocブロックの例としても機能します。
10 *
11 * @param int $position ドキュメントの位置関係を示すビットマスク(例: Dom\Node::DOCUMENT_POSITION_DISCONNECTED)。
12 * @return array<string> 位置関係の説明文の配列。
13 */
14function decodeDocumentPosition(int $position): array
15{
16    $descriptions = [];
17
18    // positionが0の場合は、2つのノードが同じであることを示します
19    if ($position === 0) {
20        $descriptions[] = "同じノード (Same Node)";
21        return $descriptions;
22    }
23
24    // ビットマスクを個々の定数と比較し、該当する説明を追加します
25    if (($position & Dom\Node::DOCUMENT_POSITION_DISCONNECTED) === Dom\Node::DOCUMENT_POSITION_DISCONNECTED) {
26        $descriptions[] = "切断されている (Disconnected)";
27    }
28    if (($position & Dom\Node::DOCUMENT_POSITION_PRECEDING) === Dom\Node::DOCUMENT_POSITION_PRECEDING) {
29        $descriptions[] = "前に位置する (Preceding)";
30    }
31    if (($position & Dom\Node::DOCUMENT_POSITION_FOLLOWING) === Dom\Node::DOCUMENT_POSITION_FOLLOWING) {
32        $descriptions[] = "後に位置する (Following)";
33    }
34    if (($position & Dom\Node::DOCUMENT_POSITION_CONTAINS) === Dom\Node::DOCUMENT_POSITION_CONTAINS) {
35        $descriptions[] = "包含する (Contains)";
36    }
37    if (($position & Dom\Node::DOCUMENT_POSITION_CONTAINED_BY) === Dom\Node::DOCUMENT_POSITION_CONTAINED_BY) {
38        $descriptions[] = "包含される (Contained By)";
39    }
40    if (($position & Dom\Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) === Dom\Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) {
41        $descriptions[] = "実装固有 (Implementation Specific)";
42    }
43
44    return $descriptions;
45}
46
47/**
48 * Dom\Node::compareDocumentPosition メソッドの使用例を示します。
49 *
50 * この関数は、Dom\Document オブジェクトとその子ノード、あるいは異なるドキュメントのノード間で
51 * の位置関係を比較し、その結果を詳細に出力します。
52 * 本来は Dom\Node のメソッドですが、Dom\Document も Dom\Node を継承しているため、
53 * Dom\Document インスタンスからこのメソッドを呼び出すことも可能です。
54 *
55 * phpDocumentor を利用する際、このようにコードの意図や引数、戻り値の情報を
56 * PHPDoc形式で記述することで、自動的にドキュメントが生成されます。
57 * PHPDocは、ドキュメント生成の「オプション」としてコードの理解を深める重要な要素です。
58 *
59 * @return void 結果を標準出力に出力します。
60 */
61function demonstrateCompareDocumentPositionExamples(): void
62{
63    echo "--- Dom\\Node::compareDocumentPosition メソッドの使用例 ---\n\n";
64
65    // ドキュメント1を作成し、DOMツリーを構築します
66    $document1 = new Dom\Document();
67    $document1->loadHTML('
68        <div id="container1">
69            <span id="elementA">Hello</span>
70            <em id="elementB">World</em>
71        </div>
72    ');
73
74    // 比較対象のノードを取得します
75    $container1 = $document1->getElementById('container1');
76    $elementA = $document1->getElementById('elementA');
77    $elementB = $document1->getElementById('elementB');
78
79    // ドキュメント2を作成し、異なるドキュメントのノードを用意します
80    $document2 = new Dom\Document();
81    $document2->loadHTML('<p id="elementC">Different Document Node</p>');
82    $elementC = $document2->getElementById('elementC');
83
84
85    // 1. 同じノード同士の比較
86    // Dom\Document オブジェクト自体は Dom\Node を継承しており、自身と比較することも可能です。
87    echo "比較: document1 (ルートノード) と document1 (ルートノード)\n";
88    $position = $document1->compareDocumentPosition($document1);
89    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
90
91    // 2. 親ノードと子ノードの比較 (親から子へ)
92    // この場合、$container1 (Dom\Element) が基準となり、$elementA (Dom\Element) との関係を比較します。
93    // $elementA は $container1 の子孫であるため、「Following (後)」かつ「Contained By (包含される)」の関係になります。
94    echo "比較: 'container1' と 'elementA'\n";
95    $position = $container1->compareDocumentPosition($elementA);
96    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
97
98    // 3. 子ノードと親ノードの比較 (子から親へ)
99    // $elementA (Dom\Element) が基準となり、$container1 (Dom\Element) との関係を比較します。
100    // $container1 は $elementA の祖先であるため、「Preceding (前)」かつ「Contains (包含する)」の関係になります。
101    echo "比較: 'elementA' と 'container1'\n";
102    $position = $elementA->compareDocumentPosition($container1);
103    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
104
105    // 4. 兄弟ノードの比較 (elementA から elementB へ)
106    // $elementA が基準となり、$elementB との関係を比較します。
107    // $elementB は $elementA の後に続く兄弟ノードであるため、「Following (後)」の関係になります。
108    echo "比較: 'elementA' と 'elementB'\n";
109    $position = $elementA->compareDocumentPosition($elementB);
110    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
111
112    // 5. 兄弟ノードの比較 (elementB から elementA へ)
113    // $elementB が基準となり、$elementA との関係を比較します。
114    // $elementA は $elementB の前に位置する兄弟ノードであるため、「Preceding (前)」の関係になります。
115    echo "比較: 'elementB' と 'elementA'\n";
116    $position = $elementB->compareDocumentPosition($elementA);
117    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
118
119    // 6. 異なるドキュメントのノードとの比較
120    // $elementA は document1 に属し、$elementC は document2 に属します。
121    // 異なるドキュメントのノードであるため、「Disconnected (切断されている)」の関係になります。
122    echo "比較: 'elementA' (document1) と 'elementC' (document2)\n";
123    $position = $elementA->compareDocumentPosition($elementC);
124    echo "結果: " . implode(", ", decodeDocumentPosition($position)) . " (コード: " . $position . ")\n\n";
125}
126
127// サンプルコードを実行します
128demonstrateCompareDocumentPositionExamples();

PHPのDom\Document::compareDocumentPositionメソッドは、二つのDOMノード間の相対的な位置関係を比較するために利用されます。このメソッドは、呼び出し元のノード(Dom\DocumentDom\Nodeを継承しているため、ドキュメント自体もノードとして比較できます)と、引数で渡されるDom\Node $otherとの位置を調べます。

メソッドの戻り値は整数値のビットマスクで、これは複数の位置関係(例:ノードが前にある、後ろにある、包含している、包含されている、切断されているなど)を組み合わせた情報を含んでいます。サンプルコードでは、この複雑なビットマスクをdecodeDocumentPosition関数を使って、人間が理解しやすい具体的な説明文の配列に変換して表示しています。

demonstrateCompareDocumentPositionExamples関数では、同じドキュメント内の親子ノードや兄弟ノード、さらには異なるドキュメントに属するノード同士を比較し、それぞれがどのような位置関係にあるかを具体的に示しています。これにより、ノードが互いにどのような関係性を持っているかを明確に把握できます。

また、サンプルコードに記述されているPHPDocブロックは、phpDocumentorのようなツールがコードから自動的にドキュメントを生成する際の重要な「オプション」であり、メソッドの目的、引数、戻り値などを記述することで、コードの可読性を高め、チーム開発での情報共有を効率化するのに役立ちます。

Dom\Document::compareDocumentPositionメソッドは、二つのDOMノード間の位置関係を整数値のビットマスクで返します。この戻り値を正しく解釈するためには、サンプルコードのdecodeDocumentPosition関数の例のように、ビットAND演算子(&)を使って個々の定数と比較する必要があります。単純な数値比較では意図しない結果になるため注意してください。このメソッドは厳密にはDom\Nodeクラスのメソッドですが、Dom\DocumentDom\Nodeを継承しているため、Dom\Documentインスタンスから呼び出すことができます。異なるドキュメントに属するノードを比較した場合、関係は常に「切断されている (Disconnected)」と判定されます。コードの理解を助け、将来のメンテナンス性を高めるために、サンプルコードのようにPHPDocコメントを適切に記述することが重要です。これはphpDocumentorでのドキュメント生成においても必須となる要素です。

関連コンテンツ

関連IT用語

関連プログラミング言語