Finder: File Search

Need to find files matching a certain mask? The Finder can help you. It's a versatile and fast tool for browsing the directory structure.

Installation:

composer require nette/utils

The examples assume an alias has been created:

use Nette\Utils\Finder;

Using

First, let's see how you can use Nette\Utils\Finder to list the file names with the extensions .txt and .md in the current directory:

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

The default directory for the search is the current directory, but you can change it using the in() or from() methods. The $file variable is an instance of the FileInfo class with lots of useful methods. The $name key contains the path to the file as a string.

What to Search For?

In addition to the findFiles() method, there is also findDirectories(), which searches only directories, and find(), which searches both. These methods are static, so they can be called without creating an instance. The mask parameter is optional, if you don't specify it, everything is searched.

foreach (Finder::find() as $file) {
	echo $file; // now all files and directories are listed
}

Use the files() and directories() methods to add what else to search for. The methods can be called repeatedly and an array of masks can be provided as a parameter:

Finder::findDirectories('vendor') // all directories
	->files(['*.php', '*.phpt']); // plus all PHP files

An alternative to static methods is to create an instance using new Finder (the fresh object created this way does not search for anything) and specify what to search for using files() and directories():

(new Finder)
	->directories()      // all directories
	->files('*.php');    // plus all PHP files

You can use wildcards *, **, ? and [...] in the mask. You can even specify in directories, for example src/*.php will search for all PHP files in the src directory.

The default search directory is the current directory. You can change this by using the in() and from() methods. As you can see from the method names, in() searches only the current directory, while from() searches its subdirectories too (recursively). If you want to search recursively in the current directory, you can use from('.').

These methods can be called multiple times or you can pass multiple paths to them as arrays, then files will be searched in all directories. If one of the directories does not exist, a Nette\UnexpectedValueException is thrown.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // searches directly in src/ and tests/
	->from('vendor');      // searches also in vendor/ subdirectories

Relative paths are relative to the current directory. Of course, absolute paths can also be specified:

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

Wildcards wildcards *, **, ? can be used in the path. For example, you can use the path src/*/*.php to search for all PHP files in the second level directories in the src directory. The ** character, called globstar, is a powerful trump card because it allows you to search subdirectories as well: use src/**/tests/*.php to search for all PHP files in the tests directory located in src or any of its subdirectories.

On the other hand, wildcards [...] characters are not supported in the path, i.e. they have no special meaning to avoid unwanted behavior in case you search for example in(__DIR__) and by chance [] characters appear in the path.

When searching files and directories in depth, the parent directory is returned first and then the files contained in it, which can be reversed with childFirst().

Wildcards

You can use several special characters in the mask:

  • * – replaces any number of arbitrary characters (except /)
  • ** – replaces any number of arbitrary characters including / (i.e. can be searched multi-level)
  • ? – replaces one arbitrary character (except /)
  • [a-z] – replaces one character from the list of characters in square brackets
  • [!a-z] – replaces one character outside the list of characters in square brackets

Examples of use:

  • img/?.png – files with the single-letter name 0.png, 1.png, x.png, etc.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – log files in the format YYYY-MM-DD
  • src/**/tests/* – files in the directory src/tests, src/foo/tests, src/foo/bar/tests and so on.
  • docs/**.md – all files with the extension .md in all subdirectories of the directory docs

Excluding

Use the exclude() method to exclude files and directories from searches. You specify a mask that the file must not match. Example of searching for files *.txt except those containing the letter X in the name:

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

Use exclude() to skip browsed subdirectories:

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

Filtering

The Finder offers several methods for filtering the results (i.e. reducing them). You can combine them and call them repeatedly.

Use size() to filter by file size. In this way, we find files with sizes between 100 and 200 bytes:

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

The date() method filters by the date the file was last modified. The values can be absolute or relative to the current date and time, for example, this is how to find files changed in the last two weeks:

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

Both functions understand the operators >, >=, <, <=, =, !=, <>.

The Finder also allows you to filter results using custom functions. The function receives an Nette\Utils\FileInfo object as a parameter and must return true to include the file in the results.

Example: search for PHP files that contain the string Nette (case-insensitive):

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

Depth Filtering

When searching recursively, you can set the maximum crawl depth using the limitDepth() method. If you set limitDepth(1), only the first subdirectories are crawled, limitDepth(0) disables depth crawling, and a value of –1 cancels the limit.

The Finder allows you to use its own functions to decide which directory to enter when browsing. The function receives an Nette\Utils\FileInfo object as a parameter and must return true to enter the directory:

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

Sorting

The Finder also offers several functions for sorting results.

The sortByName() method sorts results by file name. The sorting is natural, i.e. it correctly handles the numbers in the names and returns e.g. foo1.txt before foo10.txt.

The Finder also allows you to sort using a custom function. It takes two Nette\Utils\FileInfo objects as parameters and must return the result of the comparison with the operator <=>, i.e. -1, 0 nebo 1. For example, this is how we sort files by size:

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

Multiple Different Searches

If you need to find multiple different files in different locations or that meet different criteria, use the append() method. It returns a new Finder object so you can chain method calls:

($finder = new Finder) // store the first Finder in the $finder variable!
	->files('*.php')   // search for *.php files in src/
	->from('src')
	->append()
	->files('*.md')    // in docs/ look for *.md files
	->from('docs')
	->append()
	->files('*.json'); // in the current folder look for *.json files

Alternatively, you can use the append() method to add a specific file (or an array of files). Then it returns the same object Finder:

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

FileInfo

Nette\Utils\FileInfo is a class representing a file or directory in the search results. It is an extension of the SplFileInfo class that provides information such as file size, last modified date, name, path, etc.

Additionally, it provides methods for returning relative paths, which is useful when browsing in depth:

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

You also have methods for reading and writing the contents of a file:

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

Returning Results as an Array

As seen in the examples, the Finder implements the IteratorAggregate interface , so you can use foreach to browse the results. It's programmed so that results are only loaded as you browse, so if you have a large number of files, it doesn't wait for them all to be read.

You can also have the results returned as an array of Nette\Utils\FileInfo objects, using the collect() method. The array is not associative, but numeric.

$array = $finder->findFiles('*.php')->collect();