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
.
Символните връзки (symlinks) също се считат за директории или файлове.
Къде да се търси?
Директорията за търсене по подразбиране е текущата директория.
Можете да я промените с методите 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($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();