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.
Symlinks are also considered directories or files.
Where to Search?
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 name0.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 formatYYYY-MM-DD
src/**/tests/*
– files in the directorysrc/tests
,src/foo/tests
,src/foo/bar/tests
and so on.docs/**.md
– all files with the extension.md
in all subdirectories of the directorydocs
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();