【PHP8.x】RecursiveIteratorIterator::CHILD_FIRST定数の使い方
CHILD_FIRST定数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『CHILD_FIRST定数は、RecursiveIteratorIteratorクラスの動作モードを指定するための一つの値を表す定数です。この定数を指定すると、イテレータは木構造のような再帰的なデータ構造を走査する際に、まず子要素をすべて処理し、その後に親要素を処理するようになります。この処理順序は、一般的に「帰りがけ順(post-order traversal)」として知られています。この定数は、RecursiveIteratorIteratorクラスのインスタンスを生成する際のコンストラクタの第2引数に渡して使用します。具体的な利用例として、ディレクトリとその中にあるファイルやサブディレクトリをすべて削除する処理が挙げられます。この場合、先に中身である子要素を削除してからでなければ、親であるディレクトリを削除することはできません。CHILD_FIRSTモードを使用することで、このような構造の末端要素からルート要素に向かって順に処理を実行するロジックを簡単に実装できます。対照的に、親要素を先に処理するSELF_FIRSTモードもあり、実現したい処理に応じて適切なモードを選択することが重要です。』
構文(syntax)
1<?php 2 3$data = [ 4 'root_1', 5 'parent_1' => [ 6 'child_1', 7 'child_2', 8 ], 9 'root_2', 10]; 11 12$arrayIterator = new RecursiveArrayIterator($data); 13 14// 子要素を先に処理し、その後に親要素を処理するモードでイテレータを作成します。 15$iterator = new RecursiveIteratorIterator( 16 $arrayIterator, 17 RecursiveIteratorIterator::CHILD_FIRST 18); 19 20foreach ($iterator as $key => $value) { 21 $indent = str_repeat(' ', $iterator->getDepth()); 22 if ($iterator->hasChildren()) { 23 echo $indent . "$key (親要素)\n"; 24 } else { 25 echo $indent . "$key => $value (子要素)\n"; 26 } 27}
引数(parameters)
引数なし
引数はありません
戻り値(return)
int
RecursiveIteratorIterator::CHILD_FIRST は、イテレータの走査順序を指定する定数です。この定数を使用すると、親要素よりも子要素を先に走査するようになります。
サンプルコード
RecursiveIteratorIterator::CHILD_FIRSTで配列を子から辿る
1<?php 2 3declare(strict_types=1); 4 5/** 6 * RecursiveIteratorIterator::CHILD_FIRST を使用して、 7 * 多次元配列を子の要素から先にたどるサンプルコード。 8 * 9 * このモードでは、再帰的な処理において、親ノードよりも先に 10 * 子ノード(深い階層の要素)が処理されます。 11 * 12 * @param array<string, mixed> $data 探索する多次元配列 13 */ 14function traverseWithChildFirst(array $data): void 15{ 16 // 配列を再帰的に処理するためのイテレータを作成します。 17 $arrayIterator = new RecursiveArrayIterator($data); 18 19 // RecursiveIteratorIteratorを作成し、第2引数に CHILD_FIRST を指定します。 20 // これにより、イテレーションの順序が「子」が「親」より先になります。 21 $iterator = new RecursiveIteratorIterator( 22 $arrayIterator, 23 RecursiveIteratorIterator::CHILD_FIRST 24 ); 25 26 echo "子要素から親要素へ辿る処理順序:\n"; 27 echo "--------------------------------\n"; 28 29 // イテレータを使って配列の全要素をループ処理します。 30 foreach ($iterator as $key => $value) { 31 // getDepth() で現在の階層の深さを取得し、インデントを付けます。 32 $indent = str_repeat(' ', $iterator->getDepth()); 33 34 // isArray() で現在の要素が配列(親ノード)かどうかを判定します。 35 if (is_array($value)) { 36 // 親ノード(配列)のキーを出力します。 37 echo "{$indent}[{$key}] (親)\n"; 38 } else { 39 // 子ノード(末端の要素)のキーと値を出力します。 40 echo "{$indent}{$key} => {$value}\n"; 41 } 42 } 43} 44 45// サンプル用の多次元配列(組織図のような階層構造) 46$organization = [ 47 '経営本部' => [ 48 '人事部' => [ 49 '採用チーム' => '佐藤', 50 '労務チーム' => '鈴木' 51 ], 52 '経理部' => '高橋' 53 ], 54 '開発本部' => [ 55 '第一開発部' => '田中', 56 '第二開発部' => [ 57 'Webチーム' => '伊藤', 58 'アプリチーム' => '渡辺' 59 ] 60 ] 61]; 62 63// 関数を実行します。 64traverseWithChildFirst($organization); 65 66?>
このPHPサンプルコードは、RecursiveIteratorIteratorクラスの定数CHILD_FIRSTを使い、多次元配列を「子」の要素から先に処理する方法を示しています。
traverseWithChildFirst関数は、引数$dataで受け取った多次元配列を処理します。関数内ではまず、配列を再帰的に扱えるRecursiveArrayIteratorを作成します。次に、そのオブジェクトを元にRecursiveIteratorIteratorを作成しますが、このとき第二引数にRecursiveIteratorIterator::CHILD_FIRSTを指定するのが重要な点です。この定数を指定すると、繰り返し処理の順序が通常とは逆になり、階層が最も深い要素から順にアクセスし、最後にその親要素が処理されるようになります。
foreachループでは、この順序に従って配列の全要素を一つずつ取り出します。getDepthメソッドで現在の階層の深さを取得してインデントを調整し、is_array関数で要素が親(配列)か子(値)かを判定することで、処理の順序を分かりやすく出力しています。
このコードを実行すると、組織図の末端の担当者名が先に表示され、その後に所属するチームや部といった親要素が表示されていくのが確認できます。このように、階層構造の末端から根元に向かってデータを処理したい場合にCHILD_FIRSTモードは非常に便利です。
RecursiveIteratorIterator::CHILD_FIRSTは、多次元配列などを再帰的に処理する際の順序を指定する定数です。この指定により、通常のループとは異なり、階層の最も深い要素(子)から処理が始まり、その後で親要素が処理されます。サンプルコードの出力が、末端の担当者から部、本部へと遡る順序になるのはこのためです。この機能は、配列をイテレータに変換するRecursiveArrayIteratorと組み合わせて利用します。注意点として、デフォルト設定では末端の値のみがループ対象ですが、CHILD_FIRSTを指定することで親要素である配列自体も処理対象に含めることができます。階層構造を持つデータを、子の処理が完了してから親を処理したい場合に有効です。
PHP SimpleXMLCHILD_FIRSTでXMLを処理する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * SimpleXMLElementをRecursiveIteratorIterator::CHILD_FIRSTで処理します。 7 * 8 * キーワード「get first child」は、通常 `$xml->children()[0]` のように 9 * 特定ノードの最初の子を直接取得することを指します。 10 * 11 * このサンプルでは、与えられた定数 `RecursiveIteratorIterator::CHILD_FIRST` を使い、 12 * XMLツリー全体を再帰的に走査し、子ノードから親ノードの順で処理する方法を示します。 13 * これは、ツリーの末端から何らかの集計や処理を行いたい場合に便利です。 14 */ 15function processXmlChildFirst(): void 16{ 17 $xmlString = <<<XML 18 <books> 19 <book category="Web"> 20 <title>Learning PHP</title> 21 <author>John Doe</author> 22 </book> 23 <book category="Cooking"> 24 <title>Everyday Italian</title> 25 <author>Jane Smith</author> 26 </book> 27 </books> 28 XML; 29 30 // SimpleXMLIteratorはRecursiveIteratorを実装しているため、 31 // RecursiveIteratorIteratorと組み合わせて使用します。 32 $sxi = new SimpleXMLIterator($xmlString); 33 34 // イテレータを CHILD_FIRST モードで作成します。 35 // これにより、子(title, author)が先に処理され、次に親(book)が処理されます。 36 $iterator = new RecursiveIteratorIterator( 37 $sxi, 38 RecursiveIteratorIterator::CHILD_FIRST 39 ); 40 41 echo "XMLツリーを子要素から順に処理した結果:\n"; 42 echo "====================================\n"; 43 44 foreach ($iterator as $nodeName => $node) { 45 // インデントで階層を表現 46 $indent = str_repeat(' ', $iterator->getDepth()); 47 48 // テキストコンテンツを持つ末端の要素のみ値を出力 49 if (trim((string) $node) !== '') { 50 echo "{$indent}{$nodeName}: " . (string) $node . "\n"; 51 } 52 } 53} 54 55processXmlChildFirst();
このPHPサンプルコードは、RecursiveIteratorIteratorクラスと定数CHILD_FIRSTを使い、XMLデータの階層構造を子要素から親要素の順で再帰的に処理する方法を示します。
RecursiveIteratorIteratorは、配列やオブジェクトのような入れ子構造を持つデータを順番にたどるための機能です。その動作モードは、コンストラクタの第2引数に渡す定数によって決まります。
ここで使用されているRecursiveIteratorIterator::CHILD_FIRSTは、処理順序を「子を先に (child first)」と指定するための整数型の定数です。この定数を引数に指定すると、イテレータはツリー構造の最も末端にある要素から処理を開始し、その後でそれらの親要素を処理します。
サンプルコードでは、まずSimpleXMLIteratorでXMLを解析し、反復処理が可能なオブジェクトを生成します。次に、そのオブジェクトとCHILD_FIRST定数をRecursiveIteratorIteratorに渡してインスタンス化します。foreachでこのイテレータを処理すると、<title>や<author>といった子要素が先にアクセスされ、その後に親である<book>要素がアクセスされるという、通常とは逆の順序で処理が進みます。この方法は、ツリーの末端のデータから集計などを行いたい場合に役立ちます。
RecursiveIteratorIterator::CHILD_FIRST定数を指定すると、XMLツリーを走査する際の処理順序が、通常の「親から子」ではなく「子から親」へと逆転します。これは、ツリーの末端にある要素の値を使って集計や加工を行いたい場合に役立ちます。注意点として、このモードではbookのような親要素もイテレーションの対象になりますが、その子要素(titleやauthor)の処理がすべて完了した後に処理されます。そのため、サンプルコードのようにテキストを持たない親要素と末端の子要素を区別するために、trim((string) $node)のような条件チェックが必要になることがあります。また、この機能はRecursiveIteratorを実装したSimpleXMLIteratorと組み合わせて使用します。実際のアプリケーションでは、不正なXMLデータを読み込むとエラーが発生するため、try-catchブロックで囲んで例外処理を行うことを推奨します。