Finder: ファイルの検索

特定のマスクに一致するファイルを見つける必要がありますか?Finderが役立ちます。これは、ディレクトリ構造を探索するための多目的で高速なツールです。

インストール:

composer require nette/utils

例では、エイリアスが作成されていることを前提としています。

use Nette\Utils\Finder;

使用法

まず、Nette\Utils\Finderを使用して、現在のディレクトリにある拡張子.txt.mdのファイル名を出力する方法を示します。

foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) {
	echo $file;
}

検索のデフォルトディレクトリは現在のディレクトリですが、in() または from()メソッドを使用して変更できます。 変数$fileは、多くの便利なメソッドを持つFileInfoクラスのインスタンスです。キー$nameには、ファイルへのパスが文字列として含まれています。

何を検索するか?

findFiles()メソッドに加えて、ディレクトリのみを検索するfindDirectories()と、両方を検索するfind()もあります。これらのメソッドは静的なので、インスタンスを作成せずに呼び出すことができます。マスク付きのパラメータはオプションであり、指定しない場合はすべてが検索されます。

foreach (Finder::find() as $file) {
	echo $file; // これですべてのファイルとディレクトリが出力されます
}

files()およびdirectories()メソッドを使用して、さらに検索するものを追加できます。メソッドは繰り返し呼び出すことができ、パラメータとしてマスクの配列を指定することもできます。

Finder::findDirectories('vendor') // すべてのディレクトリ
	->files(['*.php', '*.phpt']); // さらにすべてのPHPファイル

静的メソッドの代替として、new Finderを使用してインスタンスを作成し(このように作成された新しいオブジェクトは何も検索しません)、files()およびdirectories()を使用して検索対象を指定します。

(new Finder)
	->directories()      // すべてのディレクトリ
	->files('*.php');    // さらにすべてのPHPファイル

マスクでは、ワイルドカード ***?[...]を使用できます。ディレクトリを指定することもできます。たとえば、src/*.phpsrcディレクトリ内のすべてのPHPファイルを検索します。

シンボリックリンクもディレクトリまたはファイルと見なされます。

どこを検索するか?

検索のデフォルトディレクトリは現在のディレクトリです。in()およびfrom()メソッドを使用して変更します。メソッド名からわかるように、in()はそのディレクトリ内のみを検索し、from()はそのサブディレクトリも(再帰的に)検索します。現在のディレクトリを再帰的に検索したい場合は、from('.')を使用できます。

これらのメソッドは複数回呼び出すか、複数のパスを配列として渡すことができ、ファイルはすべてのディレクトリで検索されます。ディレクトリのいずれかが存在しない場合、例外Nette\UnexpectedValueExceptionがスローされます。

Finder::findFiles('*.php')
	->in(['src', 'tests']) // src/ および tests/ 内を直接検索
	->from('vendor');      // vendor/ のサブディレクトリも検索

相対パスは現在のディレクトリからの相対パスです。もちろん、絶対パスを指定することもできます。

Finder::findFiles('*.php')
	->in('/var/www/html');

パスでは、ワイルドカード ***?を使用できます。たとえば、パスsrc/*/*.phpを使用して、srcディレクトリの第2レベルのディレクトリにあるすべてのPHPファイルを検索できます。**文字(globstarと呼ばれる)は強力な切り札であり、サブディレクトリも検索できます。src/**/tests/*.phpを使用すると、srcまたはその任意のサブディレクトリにあるtestsディレクトリ内のすべてのPHPファイルを検索します。

逆に、パス内のワイルドカード[...]はサポートされていません。つまり、特別な意味を持ちません。これは、たとえばin(__DIR__)を検索し、パスに偶然[]文字が含まれている場合に望ましくない動作が発生するのを防ぐためです。

ファイルとディレクトリの両方を深く検索する場合、最初に親ディレクトリが返され、次にその中のファイルが返されます。これはchildFirst()を使用して逆にすることができます。

ワイルドカード

マスクでは、いくつかの特殊文字を使用できます。

  • * – 任意の数の任意の文字(/を除く)を置き換えます
  • ** – /を含む任意の数の任意の文字を置き換えます(つまり、複数レベルで検索できます)
  • ? – 任意の1文字(/を除く)を置き換えます
  • [a-z] – 角括弧内の文字リストから1文字を置き換えます
  • [!a-z] – 角括弧内の文字リスト以外の1文字を置き換えます

使用例:

  • img/?.png – 1文字の名前を持つファイル 0.png1.pngx.png など。
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – YYYY-MM-DD形式のログ
  • src/**/tests/* – src/testssrc/foo/testssrc/foo/bar/testsなどのディレクトリ内のファイル。
  • docs/**.md – docsディレクトリのすべてのサブディレクトリにある拡張子.mdを持つすべてのファイル

除外

exclude()メソッドを使用すると、検索からファイルとディレクトリを除外できます。ファイルが一致してはならないマスクを指定します。例:名前に文字Xを含むものを除く*.txtファイルの検索:

Finder::findFiles('*.txt')
	->exclude('*X*');

探索するサブディレクトリを除外するには、exclude()を使用します。

Finder::findFiles('*.php')
	->from($dir)
	->exclude('temp', '.git')

フィルタリング

Finderは、結果をフィルタリング(つまり削減)するためのいくつかのメソッドを提供します。それらを組み合わせたり、繰り返し呼び出したりできます。

size()を使用してファイルサイズでフィルタリングします。これにより、サイズが100〜200バイトの範囲のファイルが見つかります。

Finder::findFiles('*.php')
	->size('>=', 100)
	->size('<=', 200);

date()メソッドは、ファイルの最終変更日でフィルタリングします。値は絶対値または現在の日時からの相対値にすることができます。たとえば、これにより過去2週間以内に変更されたファイルが見つかります。

Finder::findFiles('*.php')
	->date('>', '-2 weeks')
	->from($dir)

両方の関数は、演算子>>=<<==!=<>を理解します。

Finderでは、カスタム関数を使用して結果をフィルタリングすることもできます。関数はパラメータとしてNette\Utils\FileInfoオブジェクトを受け取り、ファイルが結果に含まれるためにはtrueを返す必要があります。

例:文字列Nette(大文字小文字を区別しない)を含むPHPファイルの検索:

Finder::findFiles('*.php')
	->filter(fn($file) => strcasecmp($file->read(), 'Nette') === 0);

深さによるフィルタリング

再帰検索を行う場合、limitDepth()メソッドを使用して最大探索深度を設定できます。limitDepth(1)を設定すると、最初のサブディレクトリのみが探索され、limitDepth(0)は深さ探索を無効にし、値 –1 は制限を解除します。

Finderでは、カスタム関数を使用して、探索中にどのディレクトリに入るかを決定できます。関数はパラメータとしてNette\Utils\FileInfoオブジェクトを受け取り、ディレクトリに入るためにはtrueを返す必要があります。

Finder::findFiles('*.php')
	->descentFilter(fn($file) => $file->getBasename() !== 'temp');

ソート

Finderは、結果をソートするためのいくつかの関数も提供します。

sortByName()メソッドは、ファイル名で結果をソートします。ソートは自然順であり、つまり名前の数字を正しく処理し、たとえばfoo1.txtfoo10.txtの前に返します。

Finderでは、カスタム関数を使用してソートすることもできます。これはパラメータとして2つのNette\Utils\FileInfoオブジェクトを受け取り、比較演算子<=>の結果、つまり-10、または1を返す必要があります。たとえば、これによりファイルをサイズでソートします。

$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());

複数の異なる検索

異なる場所にある、または他の基準を満たす複数の異なるファイルを見つける必要がある場合は、append()メソッドを使用します。これは新しいFinderオブジェクトを返すため、メソッド呼び出しを連鎖させることができます。

($finder = new Finder) // 変数 $finder に最初の Finder を保存します!
	->files('*.php')   // src/ で *.php ファイルを検索
	->from('src')
	->append()
	->files('*.md')    // docs/ で *.md ファイルを検索
	->from('docs')
	->append()
	->files('*.json'); // 現在のフォルダで *.json ファイルを検索

あるいは、append()メソッドを使用して特定のファイル(またはファイルの配列)を追加することもできます。その場合、同じFinderオブジェクトを返します。

$finder = Finder::findFiles('*.txt')
	->append(__FILE__);

FileInfo

Nette\Utils\FileInfoは、検索結果のファイルまたはディレクトリを表すクラスです。これは、ファイルサイズ、最終変更日、名前、パスなどの情報を提供するSplFileInfoクラスの拡張です。

さらに、相対パスを返すメソッドを提供します。これは、深さ探索を行う場合に便利です。

foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
	$absoluteFilePath = $file->getRealPath();
	$relativeFilePath = $file->getRelativePathname();
}

さらに、ファイルの内容を読み書きするためのメソッドも利用できます。

foreach ($finder as $file) {
    $contents = $file->read();
    // ...
    $file->write($contents);
}

結果を配列として返す

例で見たように、FinderはIteratorAggregateインターフェースを実装しているため、foreachを使用して結果を反復処理できます。結果は反復処理中にのみロードされるようにプログラムされているため、大量のファイルがある場合でも、すべてが読み取られるのを待つ必要はありません。

collect()メソッドを使用して、結果をNette\Utils\FileInfoオブジェクトの配列として返すこともできます。配列は連想配列ではなく、数値配列です。

$array = $finder->findFiles('*.php')->collect();
バージョン: 4.0