【PHP実践|実務向け】PHPで多階層ディレクトリを再帰的に走査するスマートな手法

導入

実務において、特定のディレクトリ配下にある全ファイルを一括で処理したり、バックアップのためにファイルパスの一覧を取得したりする場面は頻繁にあります。しかし、ディレクトリが深く階層化されている場合、単純なループ処理では対応できません。本記事では、PHPで多階層ディレクトリを効率的かつ安全に走査するための2つのアプローチを解説し、実務で採用すべき最適な手法を紹介します。

基礎知識

ディレクトリ走査を行う際、避けて通れないのが「再帰処理」です。再帰処理とは、関数の中で自分自身を呼び出すことで、階層がいくつあっても深さを意識せずに探索を続ける仕組みのことです。PHPでは標準で提供されている「SPL(Standard PHP Library)」を利用する方法と、従来のように「scandir関数」と再帰関数を組み合わせる方法が一般的です。

実装/解決策

実務環境では、可能な限り標準ライブラリである「RecursiveIteratorIterator」の使用を推奨します。理由は、メモリ効率の良さと、柔軟なフィルタリング機能にあります。一方で、ディレクトリ構造を動的に操作したい場合や、極めてシンプルなスクリプトで完結させたい場合には、従来通りの再帰関数を用いた実装が直感的で理解しやすいでしょう。

サンプルプログラム

ここでは、メンテナンス性と可読性に優れた「RecursiveIteratorIterator」を使った実装例を紹介します。

  • 指定ディレクトリ配下の全ファイルパスを取得する関数
  • @param string $dir 対象ディレクトリパス
  • @return array ファイルパスのリスト
  • /
    function getFilesRecursive(string $dir): array {
    $fileList = [];

    // RecursiveDirectoryIteratorでディレクトリを走査
    // SKIP_DOTS: 「.」「..」を除外
    // KEY_AS_PATHNAME: キーにパスをセット
    // CURRENT_AS_FILEINFO: SplFileInfoオブジェクトを返却
    $directoryIterator = new RecursiveDirectoryIterator(
    $dir,
    FilesystemIterator::SKIP_DOTS |
    FilesystemIterator::KEY_AS_PATHNAME |
    FilesystemIterator::CURRENT_AS_FILEINFO
    );

    // イテレータを再帰的に展開する
    // LEAVES_ONLY: ファイルのみを取得(ディレクトリ自体は除外)
    $iterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::LEAVES_ONLY);

    foreach ($iterator as $pathname => $info) {
    // SplFileInfoオブジェクトからパスを取得して配列に格納
    $fileList[] = $pathname;
    }

    return $fileList;
    }

    // 実行例
    $targetDir = __DIR__ . ‘/data/’;
    $files = getFilesRecursive($targetDir);
    print_r($files);
    ?>

    応用・注意点

    実務でこれらのコードを扱う際は、以下の点に注意してください。

    1. パフォーマンスとメモリ制限
    非常に巨大なディレクトリ構造を走査する場合、配列に全パスを格納するとメモリ不足(Memory Exhausted)になる可能性があります。その場合は、配列を返すのではなく、ジェネレータ(yield)を使用して、逐次的にファイルを処理するように書き換えるのがベストプラクティスです。

    2. シンボリックリンクの扱い
    RecursiveDirectoryIteratorはデフォルトでシンボリックリンクを追いかけない設定になっていますが、必要に応じて「FOLLOW_SYMLINKS」フラグを検討してください。ただし、無限ループの危険性があるため注意が必要です。

    3. パーミッションエラー
    読み取り権限のないディレクトリに遭遇すると例外が発生します。本番環境で実行する際は、try-catchブロックで例外をキャッチし、ログを出力するなどして処理が中断しないような堅牢な実装を心がけてください。

    タイトルとURLをコピーしました