Finder: Dosya Arama
Belirli bir maskeye uyan dosyaları bulmanız mı gerekiyor? Finder bu konuda size yardımcı olur. Dizin yapılarında gezinmek için çok yönlü ve hızlı bir araçtır.
Kurulum:
composer require nette/utils
Örnekler, aşağıdaki sınıf takma adının oluşturulduğunu varsayar:
use Nette\Utils\Finder;
Kullanım
Önce, geçerli dizindeki .txt ve .md uzantılı dosyaların adlarını listelemek için Nette\Utils\Finder sınıfını nasıl kullanabileceğinize
bakalım:
foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) {
echo $file;
}
Varsayılan arama dizini geçerli dizindir, ama bunu in() veya from() metotlarıyla
değiştirebilirsiniz. $file değişkeni FileInfo sınıfının bir örneği,
$name ise dosyanın yolunu tutan bir dizedir.
Yol, platformun ayırıcıları korunarak yazdığınız gibi döndürülür; bu yüzden Windows'ta sonuç / ile
\ karakterlerini karıştırabilir. Tek biçimli bir sonuca ihtiyacınız varsa
FileSystem::unixSlashes() çağırın.
Ne Aranır?
findFiles() metodunun yanında, yalnızca dizinleri arayan findDirectories() ve her ikisini de arayan
find() metotları vardır. Bu metotlar statiktir, dolayısıyla bir örnek oluşturmadan çağrılabilirler. Maske
argümanı isteğe bağlıdır; atlanırsa her şey eşleşir.
foreach (Finder::find() as $file) {
echo $file; // artık tüm dosya ve dizinler listeleniyor
}
files() ve directories() metotlarıyla aranacak başka öğeler de belirtebilirsiniz. Metotlar
defalarca çağrılabilir ve argüman olarak bir maske dizisi de verilebilir:
Finder::findDirectories('vendor') // tüm dizinler
->files(['*.php', '*.phpt']); // artı tüm PHP dosyaları
Statik metotlara alternatif olarak new Finder ile bir örnek oluşturup (bu şekilde oluşturulan nesne
başlangıçta hiçbir şey aramaz) ne aranacağını files() ve directories() ile
belirtebilirsiniz:
(new Finder)
->directories() // tüm dizinler
->files('*.php'); // artı tüm PHP dosyaları
Maskede *, **, ? ve [...] gibi joker
karakterler kullanabilirsiniz. Dizin de belirtebilirsiniz; örneğin src/*.php, src dizinindeki tüm
PHP dosyalarını bulur. Sembolik bağlar da dizin ya da dosya olarak ele alınır.
Nerede Aranır?
Varsayılan arama dizini geçerli dizindir. Bunu in() ve from() metotlarıyla değiştirirsiniz:
Finder::findFiles('*.php')
->in(['src', 'tests']) // doğrudan src/ ve tests/ içinde arar
->from('vendor'); // vendor/ alt dizinlerinde de arar
İki metot derinlik bakımından ayrılır: in() yalnızca verilen dizinin içinde arar, from() ise
onun alt dizinlerine de (özyinelemeli olarak) iner. Geçerli dizinde özyinelemeli aramak için from('.')
kullanın.
Özyinelemeyi yalnızca from() belirlemez; maskedeki ** joker karakteri de bunu tetikler,
dolayısıyla findFiles('**/*.php')->in('src') de özyinelemeli arar. Başka bir deyişle
from('src'), özyinelemeli maskeyle yazılmış in('src') için yalnızca bir kısayoldur. Bkz. Joker Karakterler.
Bu metotlar birden çok kez çağrılabilir ya da dizi olarak birden çok yol verebilirsiniz; dosyalar o zaman belirtilen tüm
dizinlerde aranır. Dizinlerden biri yoksa Nette\InvalidStateException fırlatılır.
Göreli yollar geçerli dizine göre yorumlanır, ama mutlak yollar da kullanılabilir:
Finder::findFiles('*.php')
->in('/var/www/html');
Yolda *, ** ve ? joker karakterlerini kullanabilirsiniz, ama [...]
kullanamazsınız; orada harfi harfine ele alınır. Bu, örneğin in(__DIR__) ile arama yaptığınızda ve yol
[] karakterleri içerdiğinde beklenmedik davranışı önler. Örneğin src/*/*.php, src
altındaki ikinci düzey dizinlerdeki tüm PHP dosyalarını arar.
Dosyalar ve dizinler özyinelemeli olarak (önce derinlik) aranırken önce üst dizin, ardından içerdiği dosyalar
döndürülür. Bu sıra childFirst() ile tersine çevrilebilir.
Joker Karakterler
Bir maske birkaç özel karakter içerebilir:
*–/ayırıcısı dışında herhangi sayıda karakter (tek bir dizin düzeyinde kalır)**–/dahil herhangi sayıda karakter (dizin düzeylerini aşar, aşağıya bakın)?–/dışında tam olarak bir karakter[a-z]– köşeli parantez içindeki aralıktan ya da kümeden bir karakter[!a-z]– köşeli parantez içinde olmayan bir karakter
Can alıcı ve kolayca gözden kaçan nokta: **, sıfır ya da daha fazla dizin düzeyiyle eşleşir. “En
az bir alt dizin” anlamına gelmez. Bu yüzden src/**/*.php, doğrudan src içinde duran bir dosyayla
da, birkaç düzey derine gömülmüş bir dosyayla da eşleşir. Şu ağacı düşünün:
src/
├── app.php
├── Model/
│ ├── User.php
│ └── Repository/
│ └── UserRepository.php
└── Control/
└── SignForm.php
Aşağıdaki tablo, tek tek maskelerin dosyalar ve dizinler için neyle eşleştiğini gösterir:
| Maske | Eşleşen |
|---|---|
src/*.php |
yalnızca src/app.php (doğrudan src içinde) |
src/**/*.php |
src/app.php, src/Model/User.php, src/Model/Repository/UserRepository.php (tüm
düzeyler, doğrudan src içindekiler dahil) |
src/* |
src dizininin doğrudan alt öğeleri: app.php, Model, Control |
src/** |
src altındaki her şey, dosyalar ve dizinler (src/**/* için kısayol) |
src/*/ |
src dizininin doğrudan alt dizinleri: Model, Control |
src/**/ |
herhangi bir derinlikteki tüm alt dizinler: Model,
Model/Repository, Control |
Akılda tutmaya değer iki kısayol var:
- Hemen ardından
/gelmeyen bir**,**/artı*gibi davranır. Yanisrc/**,src/**/*için;**.phpise**/*.phpiçin kısayoldur. - Sondaki eğik çizgi maskeyi yalnızca dizinlerle sınırlar. Yani
find('log/'),logadlı dizinleri döndürür, asla bu addaki bir dosyayı döndürmez. (findFiles()sondaki eğik çizgiyi reddeder, çünkü “dizin” adlı bir dosya aramak anlamsızdır.)
Başka kullanım örnekleri:
img/?.png–0.png,1.png,x.pnggibi tek harfli adı olan dosyalarlogs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log–YYYY-MM-DDbiçimindeki günlük dosyalarıdocs/**/*.md–docsiçindeki ve tüm alt dizinlerindeki.mduzantılı tüm dosyalar
Dışlama
Dosyaları ve dizinleri sonuçlardan çıkarmak için exclude() metodunu kullanın. Argüman, öğenin
eşleşmemesi gereken bir maskedir. Burada adında X harfi geçenler dışındaki *.txt
dosyalarını arıyoruz:
Finder::findFiles('*.txt')
->exclude('*X*');
Dışlama maskesi arama maskeleriyle tümüyle aynı dilbilgisini kullanır: aynı joker
karakterler, ./ ile sabitleme ve ** kısayolu. Dışlamanın kapsamını maskenin son kısmı
belirler:
| Maske | Dışlanan |
|---|---|
temp |
herhangi bir derinlikte temp adlı her dosya veya dizin |
temp/ |
yalnızca temp dizini (ve içeriği); temp adlı bir dosya korunur |
temp/* |
temp dizininin içeriği, ama temp dizininin kendisi korunur |
temp/** |
temp/* ile aynı |
Dışlanan bir dizine dolaşma sırasında hiç girilmez; bu yüzden tüm alt ağaçları dışlamak aramayı da hızlandırır. Belirli alt dizinleri şöyle atlarsınız:
Finder::findFiles('*.php')
->from($dir)
->exclude('temp', '.git');
Filtreleme
Finder, sonuçları filtrelemek (yani azaltmak) için çeşitli metotlar sunar. Bunları birleştirebilir ve defalarca çağırabilirsiniz.
size() ile dosya boyutuna göre filtreleriz. Böylece boyutu 100 ile 200 bayt arasında olan dosyaları
buluruz:
Finder::findFiles('*.php')
->size('>=', 100)
->size('<=', 200);
date() metodu, dosyanın son değiştirilme tarihine göre filtreler. Değerler mutlak tarihler ya da geçerli
tarih ve saate göre göreli olabilir. Örneğin bu, son iki hafta içinde değiştirilen dosyaları bulur:
Finder::findFiles('*.php')
->date('>', '-2 weeks')
->from($dir)
Her iki metot da >, >=, <, <=, =, !=,
<> operatörlerini anlar.
Finder, sonuçları özel callback'lerle filtrelemenize de olanak tanır. Callback, parametre olarak bir
Nette\Utils\FileInfo nesnesi alır ve dosyanın sonuçlara girmesi için true döndürmelidir.
Örnek: içinde 'Nette' dizesi geçen (büyük/küçük harf duyarsız) PHP dosyalarını arama:
Finder::findFiles('*.php')
->filter(fn($file) => str_contains(strtolower($file->read()), 'nette'));
Derinliğe Göre Filtreleme
Özyinelemeli arama sırasında en fazla dolaşma derinliğini limitDepth() metoduyla ayarlayabilirsiniz.
limitDepth(1) yalnızca alt dizinlerin ilk düzeyini dolaşır, limitDepth(0) derinlemesine dolaşmayı
tümüyle kapatır, –1 değeri ise derinlik sınırını kaldırır.
Finder, dolaşma sırasında hangi dizinlere girileceğine karar vermek için özel callback'ler kullanmanıza olanak tanır.
Callback, dizini temsil eden bir Nette\Utils\FileInfo nesnesi alır ve o dizine girilmesi için true
döndürmelidir:
Finder::findFiles('*.php')
->descentFilter(fn($file) => $file->getBasename() !== 'temp');
Okunamayan Dizinler
Finder, varsayılan olarak okuyamadığı dizinleri (örneğin yetersiz izinler nedeniyle) atlar. Bu durumlarda bunun yerine
istisna fırlatmasını istiyorsanız ignoreUnreadableDirs(false) çağırın.
Finder::findFiles('*.php')
->from($dir)
->ignoreUnreadableDirs(false);
Sıralama
Finder, sonuçları sıralamak için de çeşitli metotlar sunar.
sortByName() metodu sonuçları dosya adına göre sıralar. Sıralama doğaldır; yani adlardaki sayıları
doğru ele alır ve örneğin foo1.txt dosyasını foo10.txt dosyasından önce döndürür.
Finder, özel bir callback ile sıralamaya da olanak tanır. Callback, parametre olarak iki Nette\Utils\FileInfo
nesnesi alır ve karşılaştırmanın sonucunu <=> operatörüyle (yani -1, 0 ya da
1 olarak) döndürmelidir. Örneğin dosyaları boyuta göre şöyle sıralarız:
$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());
Birden Çok Farklı Arama
Farklı konumlarda ya da farklı ölçütlere uyan birden çok dosya kümesi bulmanız gerekiyorsa append()
metodunu kullanın. Yeni bir Finder nesnesi döndürür; böylece eklenen arama için metot çağrılarını
zincirleyebilirsiniz:
($finder = new Finder) // ilk Finder'ı $finder değişkeninde saklayın!
->files('*.php') // src/ içinde *.php dosyalarını ara
->from('src')
->append()
->files('*.md') // docs/ içinde *.md dosyalarını ara
->from('docs')
->append()
->files('*.json'); // geçerli klasörde *.json dosyalarını ara
Alternatif olarak append() metodu belirli bir dosyayı (ya da dosya dizisini) eklemek için kullanılabilir. Bu
durumda aynı Finder nesnesini döndürür:
$finder = Finder::findFiles('*.txt')
->append(__FILE__);
FileInfo
Nette\Utils\FileInfo, arama sonuçlarında bulunan bir dosyayı ya da dizini temsil eden bir sınıftır. SplFileInfo sınıfını genişletir ve dosya boyutu, son değiştirilme tarihi, ad, yol gibi bilgileri sunar.
Ayrıca özyinelemeli dolaşma sırasında işe yarayan, göreli yolu döndüren metotlar sağlar:
foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
$absoluteFilePath = $file->getRealPath();
$relativeFilePath = $file->getRelativePathname();
}
Bunun yanında dosyanın içeriğini okuyup yazmak için metotlar da vardır:
foreach ($finder as $file) {
$contents = $file->read();
// ...
$file->write($contents);
}
Sonuçları Dizi Olarak Alma
Örneklerde görüldüğü gibi Finder, IteratorAggregate arayüzünü uygular; dolayısıyla sonuçları
dolaşmak için foreach kullanabilirsiniz. Sonuçlar yalnızca dolaşma sırasında yüklenecek biçimde
tasarlanmıştır; yani çok sayıda dosyanız varsa hepsinin önceden okunmasını beklemez.
Sonuçları collect() metoduyla Nette\Utils\FileInfo nesnelerinden oluşan bir dizi olarak da
alabilirsiniz. Dizi ilişkisel değil, sayısal indekslidir.
$array = Finder::findFiles('*.php')->collect();