(PHPで)指定ディレクトリ以下を全部チェックしてファイル一覧を取得する方法
こんにちは。宇都宮です。
「特定ディレクトリ以下のファイル全てに対して処理を行うプログラム」を書く機会というのは、たまにありますね。
「PHP ファイル 再帰的」といったワードで検索すると、色々引っかかります。それぞれ一長一短あります。「特定ディレクトリ以下のファイル全てに対して処理を行うプログラム」の様々な実装例を紹介します。
1. scandir()とis_file()/is_dir()を組み合わせる
<?php
function getFileList($dir) {
$files = scandir($dir);
$files = array_filter($files, function ($file) { // 注(1)
return !in_array($file, array('.', '..'));
});
$list = array();
foreach ($files as $file) {
$fullpath = rtrim($dir, '/') . '/' . $file; // 注(2)
if (is_file($fullpath)) {
$list[] = $fullpath;
}
if (is_dir($fullpath)) {
$list = array_merge($list, getFileList($fullpath));
}
}
return $list;
}
scandir()で指定ディレクトリ配下のファイル・ディレクトリの一覧を取得し、一覧にディレクトリが含まれていればさらに掘り進む、という仕組みです。
(1) $filesに .(カレントディレクトリへの参照) ..(親ディレク トリへの参照)が含まれていると、再帰を繰り返してMaximum function nesting levelに引っかかることがあるので、これらを除外
(2) $dirに’/’や’/var/’といった文字列が入っている場合に、$fullpathが’//usr’や’/var//www’とならないよう、rtrim() で$dirの最後尾の/を取り除いている
この方法でもできなくはありませんが、考慮する必要のある事柄が多く、大変です。この方法はおすすめしません。
2. glob()を使う
<?php
function getFileList($dir) {
$files = glob(rtrim($dir, '/') . '/*');
$list = array();
foreach ($files as $file) {
if (is_file($file)) {
$list[] = $file;
}
if (is_dir($file)) {
$list = array_merge($list, getFileList($file));
}
}
return $list;
}
一見すると、1.と大差ないように見えますが、glob()はUnixシェルと同様のワイルドカードが使えます。「*.html」というパタ ーンを指定して、.htmlという拡張子のファイルを取ってくる、といったことが簡単にできます。
3. SPLを使ってみる
PHP5には、Standard PHP Library (SPL) というライブラリが付属しています。このライブラリは、PHP5.0.0以降ではデフォルトで使用可能、PHP5.3.0以降では常に使用可能となっています。
ファイル/ディレクトリ操作を便利にしてくれるクラスがあるので、紹介します。
<?php
function getFileList($dir) {
$iterator = new RecursiveDirectoryIterator($dir);
$iterator = new RecursiveIteratorIterator($iterator);
$list = array();
foreach ($iterator as $fileinfo) { // $fileinfoはSplFiIeInfoオブジェクト
if ($fileinfo->isFile()) {
$list[] = $fileinfo->getPathname();
}
}
return $list;
}
RecursiveDirectoryIteratorは、ディレクトリを再帰的に反復処理するためのクラスです。
RecursiveDirectoryIteratorオブジェクトは、それ自体は再帰的な反復処理に対応していないため、RecursiveDirectoryIteratorオブジェクトを、さらにRecursiveIteratorIteratorオブジェクトに変換する必要があります。
文章にするとややこしいですが、コード例を見れば使い方は直感的に分かると思います。
なお、上記コード例では、他のコード例との一貫性のため最終的に配列を返していますが、配列にす る必然性はありません。イテレータオブジェクトのままの方が使いやすい場面も多いでしょう。
条件に合致したファイルだけをフィルタリングしたい場合、RecursiveFilterIteratorを継承したクラスを用意します。
<?php
namespace Asial\File;
class HtmlFilterIterator extends \RecursiveFilterIterator
{
public function accept()
{
$iterator = $this->getInnerIterator();
if ($iterator->isDir()) {
return true;
}
if (1 === preg_match('/\.html$/', $iterator->current())) {
return true;
}
return false;
}
}
RecursiveFilterIteratorを継承するクラスでは、accept()メソッドを実装する必要があります。accept()の中にフィルタリング条件を記述します(この場合、「ディレクトリである」又は「ファイル名の末尾が”.html”で終わる」)。
<?php
require_once __DIR__ . '/HtmlFilterIterator.php';
function getHtmlList($dir) {
$iterator = new \RecursiveDirectoryIterator($dir);
$iterator = new \Asial\File\HtmlFilterIterator($iterator);
$iterator = new \RecursiveIteratorIterator($iterator);
$list = array();
foreach ($iterator as $fileinfo) {
if ($fileinfo->isFile())