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();
версію: 4.0