【PHP8.x】RecursiveFilterIterator::getChildren()メソッドの使い方
getChildrenメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『getChildrenメソッドは、現在のイテレータ要素が子要素を持つ場合に、その子要素を走査するための新しいイテレータを生成して返す処理を実行するメソッドです。このメソッドは、ディレクトリツリーのような階層構造を持つデータを再帰的に処理する際に重要な役割を担います。RecursiveFilterIteratorクラスのコンテキストにおいて、getChildrenメソッドが返すのは単なる子イテレータではありません。返されるのは、元のイテレータと同じフィルタリング条件が適用された、新しいRecursiveFilterIteratorのインスタンスです。この仕組みにより、階層の深い部分にある子要素に対しても、親と同じルールで一貫したフィルタリングを自動的に適用しながら走査を継続できます。例えば、特定の拡張子を持つファイルのみをサブディレクトリを含めて検索するような場合に、このメソッドが内部で呼び出されることで、条件に合わないディレクトリやファイルを効率的に除外できます。通常、hasChildrenメソッドを呼び出して子要素の存在を確認した後に、このメソッドが利用されます。
構文(syntax)
1<?php 2 3class ExampleFilter extends RecursiveFilterIterator 4{ 5 public function accept(): bool 6 { 7 // フィルタリングの条件をここに記述しますが、この例では常にtrueを返します 8 return true; 9 } 10} 11 12$data = [ 13 'first_level' => [ 14 'second_level_item_1', 15 'second_level_item_2', 16 ], 17]; 18 19$iterator = new ExampleFilter( 20 new RecursiveArrayIterator($data) 21); 22 23// イテレータを最初の要素に設定します 24$iterator->rewind(); 25 26// 現在の要素('first_level')が子を持つか確認します 27if ($iterator->hasChildren()) { 28 // 子要素をイテレートするための新しいイテレータを取得します 29 $childrenIterator = $iterator->getChildren(); 30} 31
引数(parameters)
引数なし
引数はありません
戻り値(return)
?RecursiveIterator
現在の要素の子要素を保持するRecursiveIterator、もしくは子要素が存在しない場合はnullを返します。
サンプルコード
PHP RecursiveFilterIterator::getChildren() で再帰フィルタリングする
1<?php 2 3// 一時的なディレクトリとファイルをセットアップします。 4// このコードを単体で実行できるようにするための一時的な準備です。 5$baseDir = 'temp_recursive_filter_example'; 6if (!is_dir($baseDir)) { 7 mkdir($baseDir, 0777, true); 8} 9mkdir($baseDir . '/documents', 0777, true); 10mkdir($baseDir . '/images', 0777, true); 11 12file_put_contents($baseDir . '/report.txt', 'This is a text report.'); 13file_put_contents($baseDir . '/log.log', 'This is a log file.'); 14file_put_contents($baseDir . '/documents/memo.txt', 'Another text file.'); 15file_put_contents($baseDir . '/documents/draft.log', 'Another log file.'); 16file_put_contents($baseDir . '/images/photo.jpg', 'Dummy image content.'); 17file_put_contents($baseDir . '/images/icon.png', 'Dummy icon content.'); 18 19/** 20 * .txt ファイルとディレクトリのみを許可するカスタムフィルターイテレータ。 21 * RecursiveFilterIterator を継承することで、元のイテレータからフィルタリングされた 22 * 子イテレータを取得する際に、このフィルターが自動的に適用されます。 23 */ 24class TextOnlyFilterIterator extends RecursiveFilterIterator 25{ 26 /** 27 * 現在の要素を許可するかどうかを判断します。 28 * このメソッドが true を返すと要素がイテレータに含まれ、false を返すとフィルタリングされます。 29 * 30 * @return bool 31 */ 32 public function accept(): bool 33 { 34 // 現在の要素がディレクトリの場合、またはファイルで拡張子が '.txt' の場合のみ許可します。 35 // これにより、'.log', '.jpg', '.png' などのファイルはフィルタリングされます。 36 if ($this->current()->isDir()) { 37 return true; // ディレクトリは、その中のファイルを見るために常に許可します。 38 } 39 40 if ($this->current()->isFile()) { 41 return str_ends_with($this->current()->getFilename(), '.txt'); 42 } 43 44 return false; 45 } 46 47 /** 48 * 現在の要素が子イテレータを持っているかどうかを判断します。 49 * RecursiveIteratorIterator が再帰的に次のレベルへ進むかどうかを決定するために使われます。 50 * 51 * @return bool 52 */ 53 public function hasChildren(): bool 54 { 55 // 現在の要素がディレクトリであれば、子イテレータを持つ可能性があります。 56 return $this->current()->isDir(); 57 } 58 59 /** 60 * フィルタリングされた子イテレータを返します。 61 * 62 * システムエンジニアを目指す初心者の方へ: 63 * この `getChildren()` メソッドは、`RecursiveIteratorIterator` のような上位のイテレータが 64 * ディレクトリ構造などを再帰的に走査する際に、内部的に呼び出されます。 65 * 66 * `RecursiveFilterIterator` のこのメソッドの実装は、元のイテレータ(この例では 67 * `RecursiveDirectoryIterator`)から子イテレータを取得し、その子イテレータに対しても 68 * 現在のフィルター(`TextOnlyFilterIterator`)を適用した新しいフィルターイテレータを返します。 69 * これにより、再帰的に構造全体にわたってフィルタリングが適用され続けます。 70 * 71 * 開発者が直接このメソッドを呼び出すことは稀ですが、 72 * `RecursiveFilterIterator` がどのように再帰的なフィルタリングを処理するかを理解する上で重要です。 73 * 74 * @return ?RecursiveIterator 75 */ 76 public function getChildren(): ?RecursiveIterator 77 { 78 // 親クラス(RecursiveFilterIterator)の getChildren() が内部的に 79 // $this->getInnerIterator()->getChildren() を呼び出します。 80 // その結果として得られる子イテレータに、現在のフィルターを再適用した新しいインスタンスを返します。 81 // これにより、ディレクトリの階層が深くなってもフィルタリングが機能します。 82 return new static($this->getInnerIterator()->getChildren()); 83 } 84} 85 86/** 87 * 指定されたディレクトリを再帰的に走査し、カスタムフィルターを適用してファイルとディレクトリを表示します。 88 * 89 * @param string $directoryPath 走査するディレクトリのパス 90 */ 91function walkFilteredDirectory(string $directoryPath): void 92{ 93 echo "--- フィルタリングされたディレクトリの内容 ({$directoryPath}) ---\n"; 94 95 // 1. RecursiveDirectoryIterator: 指定されたディレクトリを再帰的に走査するための基本イテレータ。 96 // FilesystemIterator::SKIP_DOTS は、'.' と '..' エントリをスキップします。 97 $directoryIterator = new RecursiveDirectoryIterator( 98 $directoryPath, 99 FilesystemIterator::SKIP_DOTS 100 ); 101 102 // 2. TextOnlyFilterIterator: RecursiveDirectoryIterator にカスタムフィルターを適用します。 103 // このフィルターは、.txt ファイルとディレクトリのみを許可します。 104 $filteredIterator = new TextOnlyFilterIterator($directoryIterator); 105 106 // 3. RecursiveIteratorIterator: フィルタリングされたイテレータを再帰的に走査します。 107 // このループの中で、内部的に TextOnlyFilterIterator の accept()、hasChildren()、 108 // そして getChildren() が呼び出され、再帰的なフィルタリングが実行されます。 109 // RecursiveIteratorIterator::SELF_FIRST は、まず親要素自身、次に子要素の順で走査することを意味します。 110 $recursiveIterator = new RecursiveIteratorIterator( 111 $filteredIterator, 112 RecursiveIteratorIterator::SELF_FIRST 113 ); 114 115 foreach ($recursiveIterator as $path => $fileInfo) { 116 $indent = str_repeat(' ', $recursiveIterator->getDepth()); 117 echo "{$indent}- {$fileInfo->getFilename()} "; 118 if ($fileInfo->isFile()) { 119 echo "[ファイル]\n"; 120 } elseif ($fileInfo->isDir()) { 121 echo "[ディレクトリ]\n"; 122 } else { 123 echo "[その他]\n"; 124 } 125 } 126 127 echo "--------------------------------------------------------\n"; 128} 129 130// サンプルコードの実行 131walkFilteredDirectory($baseDir); 132 133// 一時ファイルをクリーンアップします。 134function cleanupDirectory(string $dir): void 135{ 136 if (!is_dir($dir)) { 137 return; 138 } 139 // RecursiveIteratorIterator を CHILD_FIRST モードで使うと、 140 // まず子要素を処理してから親要素を処理するため、ディレクトリを空にしてから削除できます。 141 $files = new RecursiveIteratorIterator( 142 new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), 143 RecursiveIteratorIterator::CHILD_FIRST 144 ); 145 foreach ($files as $fileinfo) { 146 if ($fileinfo->isDir()) { 147 rmdir($fileinfo->getRealPath()); // 空になったディレクトリを削除 148 } else { 149 unlink($fileinfo->getRealPath()); // ファイルを削除 150 } 151 } 152 rmdir($dir); // 最上位のディレクトリを削除 153} 154 155cleanupDirectory($baseDir);
RecursiveFilterIteratorクラスのgetChildren()メソッドは、引数を取らず、?RecursiveIterator型の戻り値を返します。このメソッドは、RecursiveIteratorIteratorのような、ディレクトリ構造などを再帰的に走査する上位のイテレータによって内部的に呼び出されます。
サンプルコードのTextOnlyFilterIteratorクラスは、RecursiveFilterIteratorを継承し、このgetChildren()メソッドをオーバーライドしています。オーバーライドされたgetChildren()は、元のイテレータが持つ子要素のイテレータを取得し、その子イテレータに、現在のフィルタリングルール(この例では.txtファイルとディレクトリのみを許可するルール)を再適用した新しいフィルターイテレータのインスタンスを生成して返します。
この再帰的な仕組みにより、ディレクトリの階層がどれだけ深くなっても、指定されたフィルタリング条件が一貫して適用され続けます。開発者がこのメソッドを直接呼び出すことは稀ですが、RecursiveFilterIteratorがどのように再帰的な構造全体にわたってフィルタリングを実現しているかを理解する上で、重要な役割を果たしています。これにより、特定の条件に合致する要素のみを効率的に処理できます。
RecursiveFilterIterator::getChildren()メソッドは、通常、開発者が直接呼び出すことはなく、RecursiveIteratorIteratorがディレクトリ構造などを再帰的に走査する際に内部的に呼び出されます。このメソッドをカスタムクラスでオーバーライドする場合、元のイテレータ(getInnerIterator()で取得)の子イテレータに対しても、自身のフィルタリングロジック(accept()メソッドなど)を適用した新しいフィルターイテレータのインスタンスを返すように実装する必要があります。サンプルコードのようにnew static($this->getInnerIterator()->getChildren())とすることで、階層が深くなっても一貫したフィルタリングが適用され続けます。この実装を誤ると、サブディレクトリ内の要素が期待通りにフィルタリングされない可能性があるため注意が必要です。
RecursiveFilterIterator::getChildren() で子要素をフィルタリングする
1<?php 2 3/** 4 * RecursiveFilterIterator を継承してカスタムフィルタを作成します。 5 * このクラスは、PHPファイル (.php) またはディレクトリのみを許可するようにフィルタリングします。 6 * また、getChildren() メソッドの動作を示します。 7 */ 8class PhpOnlyFilterIterator extends RecursiveFilterIterator 9{ 10 /** 11 * 現在の要素がフィルタリング条件に適合するかどうかを決定します。 12 * このメソッドは RecursiveFilterIterator が各要素を処理する際に呼び出されます。 13 * 14 * @return bool 条件に適合する場合は true、そうでない場合は false。 15 */ 16 public function accept(): bool 17 { 18 $current = $this->current(); // 現在の SplFileInfo オブジェクトを取得 19 20 // ドットファイルやドットディレクトリ(. や .. 以外の . で始まるファイル/ディレクトリ)はスキップ 21 // RecursiveDirectoryIterator::SKIP_DOTS を使用している場合でも、 22 // .env や .git などの隠しファイル/ディレクトリは含まれるため、ここでフィルタリングします。 23 if (in_array($current->getBasename(), ['.', '..'])) { 24 return false; 25 } 26 if (str_starts_with($current->getBasename(), '.') && $current->isFile()) { 27 return false; 28 } 29 if (str_starts_with($current->getBasename(), '.') && $current->isDir()) { 30 return false; 31 } 32 33 // PHPファイル (.php 拡張子を持つファイル) またはディレクトリのみを許可します。 34 return ($current->isFile() && $current->getExtension() === 'php') || $current->isDir(); 35 } 36 37 /** 38 * 現在の要素がディレクトリである場合に、その子要素用のイテレータを返します。 39 * RecursiveFilterIterator::getChildren() は、元のイテレータの子要素を取得し、 40 * それを現在のフィルタリングルール (accept() メソッド) を適用して再帰的にフィルタリングした 41 * 新しい RecursiveIterator インスタンスを返します。 42 * 43 * 通常、RecursiveIteratorIterator が内部的にこのメソッドを呼び出して再帰処理を行います。 44 * 明示的に呼び出すことで、フィルタリングされた特定の子要素のみを直接操作することも可能です。 45 * 46 * @return ?RecursiveIterator フィルタリングされた子イテレータ、または子のイテレータが存在しない場合は null。 47 */ 48 public function getChildren(): ?RecursiveIterator 49 { 50 // 親クラス (RecursiveFilterIterator) の getChildren() メソッドは、 51 // 内部のイテレータの子を取得し、それを現在のフィルタで再ラップして返します。 52 // これにより、再帰的に同じフィルタリング条件が適用されます。 53 return new self($this->getInnerIterator()->getChildren()); 54 } 55} 56 57// 動作確認用のダミーディレクトリとファイルを作成します。 58// 実際の使用では、既存のディレクトリを指定してください。 59$baseDir = __DIR__ . '/recursive_filter_test_data'; 60if (!is_dir($baseDir)) { 61 mkdir($baseDir, 0777, true); 62} 63file_put_contents($baseDir . '/main.php', '<?php echo "Hello from main.php";'); 64file_put_contents($baseDir . '/config.txt', 'This is a config file.'); 65file_put_contents($baseDir . '/.env', 'APP_ENV=dev'); // 隠しファイル 66mkdir($baseDir . '/controllers', 0777, true); 67file_put_contents($baseDir . '/controllers/UserController.php', '<?php class UserController {}'); 68file_put_contents($baseDir . '/controllers/index.html', '<html></html>'); 69mkdir($baseDir . '/views', 0777, true); 70file_put_contents($baseDir . '/views/user_profile.php', '<?php // User profile view'); 71mkdir($baseDir . '/.git', 0777, true); // 隠しディレクトリ 72 73echo "--- フィルタリングされたディレクトリ構造の走査 ---\n"; 74 75// RecursiveDirectoryIterator を使用して、指定されたディレクトリを再帰的に走査するためのイテレータを作成します。 76// SKIP_DOTS フラグは、'.' と '..' ディレクトリをスキップします。 77$directoryIterator = new RecursiveDirectoryIterator( 78 $baseDir, 79 RecursiveDirectoryIterator::SKIP_DOTS 80); 81 82// 作成した PhpOnlyFilterIterator を使用して、RecursiveDirectoryIterator をフィルタリングします。 83// これにより、PHPファイルとディレクトリのみが許可されます。 84$filteredIterator = new PhpOnlyFilterIterator($directoryIterator); 85 86// RecursiveIteratorIterator を使用して、フィルタリングされたイテレータを再帰的に走査します。 87// SELF_FIRST は、親要素を先に、その後で子要素を走査することを意味します。 88$recursiveIterator = new RecursiveIteratorIterator( 89 $filteredIterator, 90 RecursiveIteratorIterator::SELF_FIRST 91); 92 93// フィルタリングされた要素をループ処理して表示します。 94foreach ($recursiveIterator as $path => $fileInfo) { 95 // 現在の深さに応じてインデントを追加し、ファイル/ディレクトリ名を表示します。 96 echo str_repeat(' ', $recursiveIterator->getDepth()); 97 echo "- " . $fileInfo->getBasename(); 98 if ($fileInfo->isFile()) { 99 echo " (ファイル)"; 100 } elseif ($fileInfo->isDir()) { 101 echo " (ディレクトリ)"; 102 } 103 echo "\n"; 104} 105 106echo "\n--- ルートレベルの子要素を直接取得して走査 (getChildren() の概念デモ) ---\n"; 107 108// RecursiveFilterIterator::getChildren() メソッドの直接的な利用例として、 109// ルートレベルのフィルタリングされた子要素だけを取得し、走査します。 110// これは RecursiveIteratorIterator を使わずに、イテレータの階層を一段ずつ手動で探索するイメージです。 111if ($filteredIterator->hasChildren()) { 112 // getChildren() を呼び出すと、フィルタリングされた子イテレータが返されます。 113 // このイテレータもまた RecursiveFilterIterator のインスタンスです。 114 $childrenIterator = $filteredIterator->getChildren(); 115 echo "ルートレベルのフィルタリングされた子要素:\n"; 116 foreach ($childrenIterator as $childPath => $childFileInfo) { 117 echo " - " . $childFileInfo->getBasename(); 118 if ($childFileInfo->isFile()) { 119 echo " (ファイル)"; 120 } elseif ($childFileInfo->isDir()) { 121 echo " (ディレクトリ)"; 122 } 123 echo "\n"; 124 // ここでさらに $childFileInfo がディレクトリの場合、 125 // $childrenIterator->getChildren() を呼び出して deeper な階層を探索することも可能です。 126 } 127} else { 128 echo "ルートレベルにフィルタリングされた子要素はありません。\n"; 129} 130 131// 動作確認のために作成したダミーディレクトリとファイルをクリーンアップします。 132function cleanUpDirectory(string $dir): void 133{ 134 if (!is_dir($dir)) { 135 return; 136 } 137 $objects = scandir($dir); 138 foreach ($objects as $object) { 139 if ($object != "." && $object != "..") { 140 if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . DIRECTORY_SEPARATOR . $object)) { 141 cleanUpDirectory($dir . DIRECTORY_SEPARATOR . $object); 142 } else { 143 unlink($dir . DIRECTORY_SEPARATOR . $object); 144 } 145 } 146 } 147 rmdir($dir); 148} 149cleanUpDirectory($baseDir); 150 151?>
PHPのRecursiveFilterIterator::getChildren()メソッドは、フィルタリングを適用しながら再帰的に子要素を取得するための機能です。このメソッドは引数を取りません。戻り値として、現在のフィルタリングルールが適用された子要素のイテレータ、つまり?RecursiveIteratorを返します。もし子要素が存在しない場合や、現在の要素がディレクトリではない場合はnullを返すことがあります。
RecursiveFilterIteratorを継承して独自のカスタムフィルタを作成する際、getChildren()メソッドをオーバーライドすることで、ディレクトリ内の子要素に対しても同じフィルタリング条件を適用した新しいイテレータを生成できます。これにより、特定の種類のファイルやディレクトリのみを階層的に深く探索する際に非常に役立ちます。
サンプルコードでは、PHPファイルとディレクトリのみを許可するPhpOnlyFilterIteratorというカスタムフィルタを定義しています。このクラスのgetChildren()メソッドは、内部イテレータの子要素を取得し、それを再度PhpOnlyFilterIteratorでラップして返しています。これにより、ディレクトリの深さに関わらず、一貫してPHPファイルやディレクトリだけを抽出するフィルタリングが適用されます。このようにして、RecursiveIteratorIteratorと組み合わせることで、指定した条件に合うファイルやディレクトリのみを効率的に再帰探索できるのです。
RecursiveFilterIterator::getChildren()メソッドをオーバーライドする際は、親クラスの内部イテレータが返す子要素を、必ず現在のフィルタクラスの新しいインスタンスで再ラップして返すようにしてください。これにより、子要素に対しても再帰的に同じフィルタリング条件(accept()メソッドのロジック)が適用され、意図しないファイルやディレクトリが含まれるのを防げます。通常、このメソッドはRecursiveIteratorIteratorによって自動的に呼び出されるため、直接操作することは稀です。戻り値はnullの可能性があるため、利用時にはhasChildren()で確認するか、適切なnullチェックを行うとより安全です。