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/*.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().

Замісники

У масці можна використовувати кілька спеціальних символів:

  • * – замінює будь-яку кількість будь-яких символів (крім /)
  • ** – замінює будь-яку кількість будь-яких символів, включно з / (тобто можна шукати багаторівнево)
  • ? – замінює один будь-який символ (крім /)
  • [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(fn($file) => $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();
версія: 4.0