Finder : recherche de fichiers
Vous cherchez des fichiers correspondant à un certain masque ? Finder peut vous aider. C'est un outil polyvalent et rapide pour parcourir des arborescences de répertoires.
Installation :
composer require nette/utils
Les exemples supposent que l'alias de classe suivant a été créé :
use Nette\Utils\Finder;
Utilisation
Voyons d'abord comment utiliser Nette\Utils\Finder
pour lister les noms des fichiers portant les extensions .txt et .md dans le répertoire courant :
foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) {
echo $file;
}
Le répertoire de recherche par défaut est le répertoire courant, mais vous pouvez le changer à l'aide des méthodes in() ou from(). La variable $file est une instance de la classe FileInfo, tandis que $name est une chaîne contenant le chemin du fichier.
Le chemin est retourné tel que vous l'avez écrit, avec les séparateurs de la plate-forme ; sous Windows, le résultat peut
donc mélanger / et \. Appelez FileSystem::unixSlashes() s'il vous faut une forme
uniforme.
Que rechercher ?
En plus de la méthode findFiles(), il existe findDirectories(), qui ne cherche que les répertoires,
et find(), qui cherche les deux. Ces méthodes sont statiques, elles peuvent donc être appelées sans créer
d'instance. L'argument du masque est facultatif ; s'il est omis, tout correspond.
foreach (Finder::find() as $file) {
echo $file; // tous les fichiers et répertoires sont maintenant listés
}
Les méthodes files() et directories() permettent d'indiquer d'autres éléments à rechercher. Elles
peuvent être appelées plusieurs fois et acceptent aussi un tableau de masques en argument :
Finder::findDirectories('vendor') // tous les répertoires
->files(['*.php', '*.phpt']); // plus tous les fichiers PHP
Une alternative aux méthodes statiques consiste à créer une instance avec new Finder (un objet ainsi créé ne
cherche d'abord rien) et à indiquer quoi chercher avec files() et directories() :
(new Finder)
->directories() // tous les répertoires
->files('*.php'); // plus tous les fichiers PHP
Dans le masque, vous pouvez utiliser les caractères génériques *,
**, ? et [...]. Vous pouvez même indiquer des répertoires : src/*.php trouve
par exemple tous les fichiers PHP du répertoire src. Les liens symboliques sont eux aussi traités comme des
répertoires ou des fichiers.
Où rechercher ?
Le répertoire de recherche par défaut est le répertoire courant. Vous le changez avec les méthodes in() et
from() :
Finder::findFiles('*.php')
->in(['src', 'tests']) // recherche directement dans src/ et tests/
->from('vendor'); // recherche aussi dans les sous-répertoires de vendor/
Les deux méthodes diffèrent par la profondeur : in() ne cherche que dans le répertoire indiqué, tandis que
from() descend aussi dans ses sous-répertoires (récursivement). Pour parcourir récursivement le répertoire
courant, utilisez from('.').
La récursivité n'est toutefois pas décidée par from() seule : le caractère générique ** dans
le masque la pilote également, si bien que findFiles('**/*.php')->in('src') cherche aussi récursivement.
Autrement dit, from('src') n'est qu'un raccourci pour in('src') avec un masque récursif. Voyez Caractères génériques.
Ces méthodes peuvent être appelées plusieurs fois, ou vous pouvez leur passer plusieurs chemins sous forme de tableau ; les
fichiers seront alors cherchés dans tous les répertoires indiqués. Si l'un des répertoires n'existe pas, une
Nette\InvalidStateException est levée.
Les chemins relatifs le sont par rapport au répertoire courant, mais les chemins absolus sont également possibles :
Finder::findFiles('*.php')
->in('/var/www/html');
Dans le chemin, vous pouvez utiliser les caractères génériques *, ** et ?, mais
pas [...], qui y est pris au pied de la lettre. Cela évite les comportements inattendus quand vous cherchez
par exemple in(__DIR__) et que le chemin contient des caractères []. Ainsi, src/*/*.php
cherche tous les fichiers PHP situés dans les répertoires de deuxième niveau sous src.
Lors d'une recherche récursive de fichiers et de répertoires (en profondeur d'abord), le répertoire parent est retourné en
premier, suivi des fichiers qu'il contient. Cet ordre peut être inversé avec childFirst().
Caractères génériques
Un masque peut contenir plusieurs caractères spéciaux :
*– un nombre quelconque de caractères, sauf le séparateur/(il reste à un seul niveau de répertoire)**– un nombre quelconque de caractères, y compris/(il traverse les niveaux de répertoires, voir ci-dessous)?– exactement un caractère, sauf/[a-z]– un caractère de l'intervalle ou de l'ensemble indiqué entre crochets[!a-z]– un caractère absent des crochets
Le point capital, facile à manquer : ** correspond à zéro ou plusieurs niveaux de répertoires. Il ne
signifie pas “au moins un sous-répertoire”. src/**/*.php correspond donc aussi bien à un fichier placé
directement dans src qu'à un fichier enfoui plusieurs niveaux plus bas. Prenons cette arborescence :
src/
├── app.php
├── Model/
│ ├── User.php
│ └── Repository/
│ └── UserRepository.php
└── Control/
└── SignForm.php
Le tableau suivant montre ce à quoi correspondent les différents masques, pour les fichiers comme pour les répertoires :
| Masque | Correspond à |
|---|---|
src/*.php |
uniquement src/app.php (directement dans src) |
src/**/*.php |
src/app.php, src/Model/User.php, src/Model/Repository/UserRepository.php (tous les
niveaux, y compris directement dans src) |
src/* |
les enfants directs de src : app.php, Model, Control |
src/** |
tout ce qui se trouve sous src, fichiers et répertoires (raccourci pour src/**/*) |
src/*/ |
les sous-répertoires directs de src : Model, Control |
src/**/ |
tous les sous-répertoires, à toute profondeur : Model,
Model/Repository, Control |
Deux raccourcis méritent d'être retenus :
- Un
**qui n'est pas immédiatement suivi de/se comporte comme**/plus*. Ainsi,src/**est un raccourci poursrc/**/*, et**.phppour**/*.php. - Une barre oblique finale restreint le masque aux seuls répertoires. Ainsi,
find('log/')retourne les répertoires nomméslog, mais jamais un fichier de ce nom. (findFiles()refuse une barre oblique finale, chercher un fichier “répertoire” n'ayant aucun sens.)
Autres exemples d'utilisation :
img/?.png– fichiers dont le nom tient en une lettre, comme0.png,1.png,x.pnglogs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log– fichiers de log au formatYYYY-MM-DDdocs/**/*.md– tous les fichiers portant l'extension.mddansdocset tous ses sous-répertoires
Exclusion
La méthode exclude() permet d'écarter des fichiers et des répertoires des résultats. L'argument est un masque
auquel l'élément ne doit pas correspondre. Ici, nous cherchons les fichiers *.txt, à l'exception de ceux
dont le nom contient la lettre X :
Finder::findFiles('*.txt')
->exclude('*X*');
Le masque d'exclusion suit exactement la même grammaire que les masques de recherche : les mêmes caractères génériques, l'ancrage ./ et le raccourci **. Sa
partie finale décide de la portée de l'exclusion :
| Masque | Exclut |
|---|---|
temp |
tout fichier ou répertoire nommé temp, à toute profondeur |
temp/ |
uniquement un répertoire temp (et son contenu) ; un fichier nommé temp est conservé |
temp/* |
le contenu de temp, mais garde le répertoire temp lui-même |
temp/** |
la même chose que temp/* |
Un répertoire exclu n'est même pas parcouru, si bien qu'exclure des sous-arbres entiers accélère aussi la recherche. C'est ainsi que vous sautez des sous-répertoires précis :
Finder::findFiles('*.php')
->from($dir)
->exclude('temp', '.git');
Filtrage
Finder propose plusieurs méthodes pour filtrer les résultats (autrement dit, les réduire). Vous pouvez les combiner et les appeler plusieurs fois.
Avec size(), nous filtrons par taille de fichier. Nous trouvons ainsi les fichiers dont la taille est comprise
entre 100 et 200 octets :
Finder::findFiles('*.php')
->size('>=', 100)
->size('<=', 200);
La méthode date() filtre par date de dernière modification du fichier. Les valeurs peuvent être des dates
absolues ou relatives à la date et à l'heure courantes. Ceci trouve par exemple les fichiers modifiés au cours des deux
dernières semaines :
Finder::findFiles('*.php')
->date('>', '-2 weeks')
->from($dir)
Les deux méthodes comprennent les opérateurs >, >=, <, <=,
=, !=, <>.
Finder permet aussi de filtrer les résultats à l'aide de callbacks personnalisés. Le callback reçoit un objet
Nette\Utils\FileInfo en paramètre et doit retourner true pour que le fichier figure dans les
résultats.
Exemple : recherche des fichiers PHP contenant la chaîne 'Nette' (sans distinction de casse) :
Finder::findFiles('*.php')
->filter(fn($file) => str_contains(strtolower($file->read()), 'nette'));
Filtrage en profondeur
Lors d'une recherche récursive, vous pouvez fixer la profondeur maximale de parcours avec la méthode
limitDepth(). limitDepth(1) ne parcourt que le premier niveau de sous-répertoires,
limitDepth(0) désactive entièrement le parcours en profondeur, et la valeur –1 supprime la limite.
Finder permet d'utiliser des callbacks personnalisés pour décider dans quels répertoires entrer pendant le parcours. Le
callback reçoit un objet Nette\Utils\FileInfo représentant le répertoire et doit retourner true pour
y entrer :
Finder::findFiles('*.php')
->descentFilter(fn($file) => $file->getBasename() !== 'temp');
Répertoires illisibles
Par défaut, Finder passe les répertoires qu'il ne peut pas lire (faute de droits suffisants, par exemple). Si vous préférez
qu'il lève une exception dans ces cas-là, appelez ignoreUnreadableDirs(false).
Finder::findFiles('*.php')
->from($dir)
->ignoreUnreadableDirs(false);
Tri
Finder offre également plusieurs méthodes pour trier les résultats.
La méthode sortByName() trie les résultats par nom de fichier. Le tri est naturel : il traite correctement les
nombres dans les noms et retourne par exemple foo1.txt avant foo10.txt.
Finder permet aussi de trier à l'aide d'un callback personnalisé. Celui-ci reçoit deux objets
Nette\Utils\FileInfo en paramètres et doit retourner le résultat de la comparaison avec l'opérateur
<=> (soit -1, 0 ou 1). Voici par exemple comment trier les fichiers par
taille :
$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());
Plusieurs recherches différentes
Si vous avez besoin de trouver plusieurs ensembles de fichiers à des emplacements différents ou répondant à des critères
différents, utilisez la méthode append(). Elle retourne un nouvel objet Finder, ce qui permet
d'enchaîner les appels de méthodes pour la recherche ajoutée :
($finder = new Finder) // stockez le premier Finder dans la variable $finder !
->files('*.php') // cherche les fichiers *.php dans src/
->from('src')
->append()
->files('*.md') // dans docs/, cherche les fichiers *.md
->from('docs')
->append()
->files('*.json'); // dans le répertoire courant, cherche les fichiers *.json
La méthode append() peut aussi servir à ajouter un fichier précis (ou un tableau de fichiers). Dans ce cas,
elle retourne le même objet Finder :
$finder = Finder::findFiles('*.txt')
->append(__FILE__);
FileInfo
Nette\Utils\FileInfo est une classe représentant un fichier ou un répertoire trouvé dans les résultats de la recherche. Elle étend la classe SplFileInfo et fournit des informations comme la taille du fichier, la date de dernière modification, le nom, le chemin, etc.
Elle fournit en outre des méthodes retournant le chemin relatif, ce qui est pratique lors d'un parcours récursif :
foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
$absoluteFilePath = $file->getRealPath();
$relativeFilePath = $file->getRelativePathname();
}
Des méthodes de lecture et d'écriture du contenu du fichier sont également disponibles :
foreach ($finder as $file) {
$contents = $file->read();
// ...
$file->write($contents);
}
Retour des résultats sous forme de tableau
Comme le montrent les exemples, Finder implémente l'interface IteratorAggregate : vous pouvez donc parcourir les
résultats avec foreach. Il est conçu pour ne charger les résultats que pendant l'itération, si bien qu'avec un
grand nombre de fichiers, il n'attend pas de les avoir tous lus au préalable.
Vous pouvez aussi obtenir les résultats sous forme de tableau d'objets Nette\Utils\FileInfo avec la méthode
collect(). Le tableau est indexé numériquement, pas associativement.
$array = Finder::findFiles('*.php')->collect();