結局、PHP でディレクトリ内のすべてのファイルを操作するにはどうしたらいいのか

PHPディレクトリ内を全走査してファイルを操作(高度なギャグ)するときの方法を調べました。 たまにしかやらないから全然身につかないです。

今回は下記のようなファイル構造で target 以下のファイルを操作する場合を考えます。

~/file_test » tree ./
./
├── file.php
└── target
    ├── dir
    │   └── fuga.png
    └── hoge.png

2 directories, 3 files

scandir

scandir は指定されたパスのファイルとディレクトリのリストを取得します。 ... が含まれていたり、下層ディレクトリ内のファイルは取得してくれません。除外したり再帰的に取得しないといけないので、ちょっと使い勝手が悪いです。 取得できる値はファイル / ディレクトリ名の文字列です

<?php
var_dump(scandir($targetDirectory));

/*
array(4) {
  [0]=>
  string(1) "."
  [1]=>
  string(2) ".."
  [2]=>
  string(3) "dir"
  [3]=>
  string(8) "hoge.png"
}
*/

DirectoryIterator

DirectoryIterator もありますが、scandir と同じく ... が含まれていたり、下層ディレクトリ内のファイルは取得してくれないです。 イテレーションして取れる値は DirectoryIterator 型です。

<?php
foreach (new \DirectoryIterator($targetDirectory) as $item) {
    var_dump($item);
}

FilesystemIterator

FilesystemIterator は DirectoryIterator に少し毛が生えた感じのやつです。 コンストラクタでフラグを指定できて、動作を変えられます。

FilesystemIterator::SKIP_DOTS のフラグ(デフォルト有効)を渡すと結果に ... が含まれなくなります。 イテレーションして取れる値は SplFileInfo 型です。

<?php
foreach (new \FilesystemIterator($targetDirectory) as $item) {
    var_dump($item);
}

RecursiveIteratorIterator と RecursiveDirectoryIterator

結論、これを使えば楽にファイルだけを操作できます。 RecursiveDirectoryIterator が下層へアクセスする getChildren を実装していて、RecursiveIteratorIterator と組み合わせることで一度のイテレーションで下層を含めたすべてのファイルを操作できます。

RecursiveDirectoryIteratorRecursiveIteratorIterator もフラグを指定できて、 下記のように、対象のディレクトリに対して何をどのように取得するかをフラグで指定できます。 RecursiveIteratorIterator::LEAVES_ONLY のフラグ(デフォルト有効)を渡すと結果はファイルだけのイテレータになります。

<?php
foreach (
    new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator(
            $targetDirectory,
            \FilesystemIterator::SKIP_DOTS // `.`, `..` をスキップ
            | \FilesystemIterator::KEY_AS_PATHNAME
            | \FilesystemIterator::CURRENT_AS_FILEINFO // SplFileInfo として取得
        ),
        \RecursiveIteratorIterator::LEAVES_ONLY // ファイルだけ取得
    ) as $item
) {
    var_dump($item);
}

フラグの指定とか長いし new がたくさん出てきたりとかするので、何度も使う場合は適当に関数でラップするといい感じになるかもしれないです。

RecursiveIteratorIterator は結構面白いことができるので、マニュアル を読んでみるといいかもしれません。


以下は、調査用に作った PHP ファイルのソースと実行結果です。

<?php
declare(strict_types=1);

$targetDirectory = 'target/';

var_dump('---- scandir ----------------------');
var_dump(scandir($targetDirectory));
var_dump('---- DirectoryIterator ------------');
foreach (new \DirectoryIterator($targetDirectory) as $item) {
    var_dump($item);
}
var_dump('---- FilesystemIterator -----------');
foreach (new \FilesystemIterator($targetDirectory) as $item) {
    var_dump($item);
}
var_dump('---- RecursiveIteratorIterator ----');
foreach (
    new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator(
            $targetDirectory,
            \FilesystemIterator::SKIP_DOTS // `.`, `..` をスキップ
            | \FilesystemIterator::KEY_AS_PATHNAME
            | \FilesystemIterator::CURRENT_AS_FILEINFO // SplFileInfo として取得
        ),
        \RecursiveIteratorIterator::LEAVES_ONLY // ファイルだけ取得
    ) as $item
) {
    var_dump($item);
}
~/file_test » php file.php
string(35) "---- scandir ----------------------"
array(4) {
  [0]=>
  string(1) "."
  [1]=>
  string(2) ".."
  [2]=>
  string(3) "dir"
  [3]=>
  string(8) "hoge.png"
}
string(35) "---- DirectoryIterator ------------"
object(DirectoryIterator)#1 (4) {
  ["pathName":"SplFileInfo":private]=>
  string(8) "target/."
  ["fileName":"SplFileInfo":private]=>
  string(1) "."
  ["glob":"DirectoryIterator":private]=>
  bool(false)
  ["subPathName":"RecursiveDirectoryIterator":private]=>
  string(0) ""
}
object(DirectoryIterator)#1 (4) {
  ["pathName":"SplFileInfo":private]=>
  string(9) "target/.."
  ["fileName":"SplFileInfo":private]=>
  string(2) ".."
  ["glob":"DirectoryIterator":private]=>
  bool(false)
  ["subPathName":"RecursiveDirectoryIterator":private]=>
  string(0) ""
}
object(DirectoryIterator)#1 (4) {
  ["pathName":"SplFileInfo":private]=>
  string(15) "target/hoge.png"
  ["fileName":"SplFileInfo":private]=>
  string(8) "hoge.png"
  ["glob":"DirectoryIterator":private]=>
  bool(false)
  ["subPathName":"RecursiveDirectoryIterator":private]=>
  string(0) ""
}
object(DirectoryIterator)#1 (4) {
  ["pathName":"SplFileInfo":private]=>
  string(10) "target/dir"
  ["fileName":"SplFileInfo":private]=>
  string(3) "dir"
  ["glob":"DirectoryIterator":private]=>
  bool(false)
  ["subPathName":"RecursiveDirectoryIterator":private]=>
  string(0) ""
}
string(35) "---- FilesystemIterator -----------"
object(SplFileInfo)#4 (2) {
  ["pathName":"SplFileInfo":private]=>
  string(15) "target/hoge.png"
  ["fileName":"SplFileInfo":private]=>
  string(8) "hoge.png"
}
object(SplFileInfo)#1 (2) {
  ["pathName":"SplFileInfo":private]=>
  string(10) "target/dir"
  ["fileName":"SplFileInfo":private]=>
  string(3) "dir"
}
string(35) "---- RecursiveIteratorIterator ----"
object(SplFileInfo)#6 (2) {
  ["pathName":"SplFileInfo":private]=>
  string(15) "target/hoge.png"
  ["fileName":"SplFileInfo":private]=>
  string(8) "hoge.png"
}
object(SplFileInfo)#8 (2) {
  ["pathName":"SplFileInfo":private]=>
  string(19) "target/dir/fuga.png"
  ["fileName":"SplFileInfo":private]=>
  string(8) "fuga.png"
}