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()
, который
ищет в обоих каталогах. Эти методы статические, поэтому их можно
вызывать без создания экземпляра. Параметр mask является
необязательным, если вы его не укажете, поиск будет производиться во
всех каталогах.
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
Вы можете использовать подстановочные знаки *
,
**
, ?
and [...]
в маске. Можно даже указывать в
каталогах, например, src/*.php
будет искать все файлы PHP в каталоге
src
.
Симлинки также считаются каталогами или файлами.
Где искать?
Директория поиска по умолчанию – это текущая директория. Вы можете
изменить это, используя методы 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');
Символы подстановки *
, **
, ?
can be used
in the path. For example, you can use the path src/*/*.php
для поиска всех файлов PHP в
каталогах второго уровня в каталоге src
. Символ **
,
называемый globstar, является мощным козырем, поскольку позволяет искать и
в подкаталогах: используйте src/**/tests/*.php
для поиска всех файлов PHP
в каталоге tests
, расположенных в src
или любом из его
подкаталогов.
С другой стороны, подстановочные символы [...]
не
поддерживаются в пути, т.е. они не имеют специального значения, чтобы
избежать нежелательного поведения в случае, если вы ищете, например,
in(__DIR__)
и случайно в пути появляются символы []
.
При глубоком поиске файлов и каталогов сначала возвращается
родительский каталог, а затем содержащиеся в нем файлы, что можно
изменить на противоположное с помощью childFirst()
.
Дикие символы
В маске можно использовать несколько специальных символов:
*
– replaces any number of arbitrary characters (except/
)**
– заменяет любое количество произвольных символов, включая/
(т.е. может осуществляться многоуровневый поиск)?
– replaces one arbitrary character (except/
)[a-z]
– заменяет один символ из списка символов в квадратных скобках[!a-z]
– заменяет один символ вне списка символов в квадратных скобках
Примеры использования:
img/?.png
– файлы с однобуквенным именем0.png
,1.png
,x.png
и т.д.logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log
– файлы журналов в форматеYYYY-MM-DD
src/**/tests/*
– файлы в директорииsrc/tests
,src/foo/tests
,src/foo/bar/tests
и т.д.docs/**.md
– все файлы с расширением.md
во всех подкаталогах каталогаdocs
Исключая
Используйте метод exclude()
для исключения файлов и каталогов из
поиска. Вы указываете маску, которой не должен соответствовать файл.
Пример поиска файлов *.txt
, кроме тех, которые содержат букву
X
в имени:
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()
фильтрует по дате последнего изменения файла.
Значения могут быть абсолютными или относительными к текущей дате и
времени, например, так можно найти файлы, измененные за последние две
недели:
Finder::findFiles('*.php')
->date('>', '-2 weeks')
->from($dir)
Обе функции понимают операторы >
, >=
, <
,
<=
, =
, !=
, <>
.
Finder также позволяет фильтровать результаты с помощью
пользовательских функций. Функция получает в качестве параметра
объект Nette\Utils\FileInfo
и должна вернуть true
, чтобы включить
файл в результаты.
Пример: поиск файлов PHP, содержащих строку Nette
(без учета
регистра):
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($file->getBasename() !== 'temp');
Сортировка
Finder также предлагает несколько функций для сортировки результатов.
Метод sortByName()
сортирует результаты по имени файла. Сортировка
является естественной, т.е. она правильно обрабатывает цифры в именах и
возвращает, например, foo1.txt
перед foo10.txt
.
Finder также позволяет сортировать с помощью пользовательской функции.
Она принимает в качестве параметров два объекта Nette\Utils\FileInfo
и
должна возвращать результат сравнения с оператором <=>
т.е.
-1
, 0
nebo 1
. Например, так мы сортируем файлы по
размеру:
$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());
Несколько разных поисков
Если вам нужно найти несколько разных файлов в разных местах или
отвечающих разным критериям, используйте метод append()
. Он
возвращает новый объект Finder
, поэтому вы можете использовать
цепочку вызовов методов:
($finder = new Finder) // сохраняем первый Finder в переменной $finder!
->files('*.php') // поиск *.php файлов в src/
->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
для просмотра результатов.
Он запрограммирован так, что результаты загружаются только по мере
просмотра, поэтому если у вас большое количество файлов, он не будет
ждать, пока все они будут прочитаны.
Вы также можете вернуть результаты в виде массива объектов
Nette\Utils\FileInfo
, используя метод collect()
. Массив не
ассоциативный, а числовой.
$array = $finder->findFiles('*.php')->collect();