Finder: поиск файлов
Вам нужно найти файлы, соответствующие определенной маске? Finder вам в этом поможет. Это универсальный и быстрый инструмент для обхода структуры каталогов.
Установка:
composer require nette/utils
Примеры предполагают созданный псевдоним (alias):
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/*.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');
В пути можно использовать метасимволы метасимволы
*
, **
, ?
. Так, например, с помощью пути src/*/*.php
вы можете искать все PHP-файлы в каталогах второго уровня в каталоге
src
. Символ **
, называемый globstar, является мощным козырем,
потому что позволяет искать и в подкаталогах: с помощью
src/**/tests/*.php
вы ищете все PHP-файлы в каталоге tests
,
находящемся в src
или любом его подкаталоге.
Наоборот, метасимволы [...]
в пути не поддерживаются, т.е. не
имеют специального значения, чтобы не возникало нежелательного
поведения в случае, если вы будете искать, например, in(__DIR__)
, и
случайно в пути будут встречаться символы []
.
При поиске файлов и каталогов в глубину сначала возвращается
родительский каталог, и только потом файлы, содержащиеся в нем, что
можно изменить с помощью childFirst()
.
Метасимволы (Wildcards)
В маске можно использовать несколько специальных символов:
*
– заменяет любое количество любых символов (кроме/
)**
– заменяет любое количество любых символов, включая/
(т.е. можно искать многоуровнево)?
– заменяет один любой символ (кроме/
)[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
Исключение (Exclusion)
С помощью метода 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
или 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
для обхода результатов. Он
запрограммирован так, что результаты загружаются только в процессе
обхода, так что если у вас большое количество файлов, не нужно ждать,
пока все они будут прочитаны.
Результаты также можно получить в виде массива объектов
Nette\Utils\FileInfo
, с помощью метода collect()
. Массив не
ассоциативный, а числовой.
$array = $finder->findFiles('*.php')->collect();