Finder : recherche de fichiers

Besoin de trouver des fichiers correspondant à un certain masque ? Finder vous y aidera. C'est un outil polyvalent et rapide pour parcourir la structure des répertoires.

Installation :

composer require nette/utils

Les exemples supposent qu'un alias a été créé :

use Nette\Utils\Finder;

Utilisation

Montrons d'abord comment vous pouvez utiliser Nette\Utils\Finder pour lister les noms de fichiers avec les extensions .txt et .md dans le répertoire courant :

foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) {
	echo $file;
}

Le répertoire par défaut pour la recherche est le répertoire courant, mais vous pouvez le modifier à l'aide des méthodes in() ou from(). La variable $file est une instance de la classe FileInfo avec de nombreuses méthodes utiles. La clé $name contient le chemin du fichier sous forme de chaîne.

Que rechercher ?

En plus de la méthode findFiles(), il existe aussi findDirectories(), qui recherche uniquement les répertoires, et find(), qui recherche les deux. Ces méthodes sont statiques, elles peuvent donc être appelées sans créer d'instance. Le paramètre avec le masque est optionnel, si vous ne le spécifiez pas, tout sera recherché.

foreach (Finder::find() as $file) {
	echo $file; // maintenant tous les fichiers et répertoires seront affichés
}

À l'aide des méthodes files() et directories(), vous pouvez compléter ce qui doit être recherché. Les méthodes peuvent être appelées de manière répétée et un tableau de masques peut également être spécifié comme paramètre :

Finder::findDirectories('vendor') // tous les répertoires
	->files(['*.php', '*.phpt']); // plus tous les fichiers PHP

Une alternative aux méthodes statiques est de créer une instance à l'aide de new Finder (un objet fraîchement créé de cette manière ne recherche rien) et de spécifier ce qu'il faut rechercher à l'aide de 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 spécifier des répertoires, par exemple src/*.php recherchera tous les fichiers PHP dans le répertoire src.

Les liens symboliques sont également considérés comme des répertoires ou des fichiers.

Où rechercher ?

Le répertoire par défaut pour la recherche est le répertoire courant. Vous le modifiez à l'aide des méthodes in() et from(). Comme les noms des méthodes l'indiquent, in() recherche uniquement dans le répertoire donné, tandis que from() recherche également dans ses sous-répertoires (récursivement). Si vous souhaitez rechercher récursivement dans le répertoire courant, vous pouvez utiliser from('.').

Ces méthodes peuvent être appelées plusieurs fois ou recevoir plusieurs chemins sous forme de tableau, les fichiers seront alors recherchés dans tous les répertoires. Si l'un des répertoires n'existe pas, une exception Nette\UnexpectedValueException sera levée.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // recherche directement dans src/ et tests/
	->from('vendor');      // recherche également dans les sous-répertoires de vendor/

Les chemins relatifs sont relatifs au répertoire courant. Il est bien sûr possible de spécifier également des chemins absolus :

Finder::findFiles('*.php')
	->in('/var/www/html');

Il est possible d'utiliser des caractères génériques *, **, ? dans le chemin. Vous pouvez ainsi, par exemple, à l'aide du chemin src/*/*.php, rechercher tous les fichiers PHP dans les répertoires de deuxième niveau du répertoire src. Le caractère **, appelé globstar, est un atout puissant, car il permet de rechercher également dans les sous-répertoires : à l'aide de src/**/tests/*.php, vous recherchez tous les fichiers PHP dans le répertoire tests situé dans src ou dans n'importe lequel de ses sous-répertoires.

Inversement, les caractères génériques [...] ne sont pas supportés dans le chemin, c'est-à-dire qu'ils n'ont pas de signification spéciale, afin d'éviter un comportement indésirable dans le cas où vous rechercheriez par exemple in(__DIR__) et que, par hasard, les caractères [] apparaîtraient dans le chemin.

Lors de la recherche de fichiers et de répertoires en profondeur, le répertoire parent est retourné en premier, puis les fichiers qu'il contient, ce qui peut être inversé à l'aide de childFirst().

Caractères génériques

Dans le masque, vous pouvez utiliser plusieurs caractères spéciaux :

  • * – remplace n'importe quel nombre de caractères quelconques (sauf /)
  • ** – remplace n'importe quel nombre de caractères quelconques y compris / (c'est-à-dire qu'il est possible de rechercher sur plusieurs niveaux)
  • ? – remplace un caractère quelconque (sauf /)
  • [a-z] – remplace un caractère de la liste de caractères entre crochets
  • [!a-z] – remplace un caractère en dehors de la liste de caractères entre crochets

Exemples d'utilisation :

  • img/?.png – fichiers avec un nom d'une seule lettre 0.png, 1.png, x.png, etc.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – logs au format YYYY-MM-DD
  • src/**/tests/* – fichiers dans les répertoires src/tests, src/foo/tests, src/foo/bar/tests et ainsi de suite.
  • docs/**.md – tous les fichiers avec l'extension .md dans tous les sous-répertoires du répertoire docs

Exclusion

À l'aide de la méthode exclude(), il est possible d'exclure des fichiers et des répertoires de la recherche. Vous spécifiez un masque auquel le fichier ne doit pas correspondre. Exemple de recherche de fichiers *.txt sauf ceux qui contiennent la lettre X dans leur nom :

Finder::findFiles('*.txt')
	->exclude('*X*');

Pour omettre les sous-répertoires parcourus, utilisez exclude() :

Finder::findFiles('*.php')
	->from($dir)
	->exclude('temp', '.git')

Filtrage

Finder offre plusieurs méthodes pour filtrer les résultats (c'est-à-dire les réduire). Vous pouvez les combiner et les appeler de manière répétée.

À l'aide de size(), nous filtrons par taille de fichier. De cette manière, nous trouvons 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 absolues ou relatives à la date et à l'heure actuelles, par exemple, de cette manière, nous trouvons les fichiers modifiés au cours des deux dernières semaines :

Finder::findFiles('*.php')
	->date('>', '-2 weeks')
	->from($dir)

Les deux fonctions comprennent les opérateurs >, >=, <, <=, =, !=, <>.

Finder permet également de filtrer les résultats à l'aide de fonctions personnalisées. La fonction reçoit comme paramètre l'objet Nette\Utils\FileInfo et doit retourner true pour que le fichier soit inclus dans les résultats.

Exemple : recherche de fichiers PHP qui contiennent la chaîne Nette (indépendamment de la casse) :

Finder::findFiles('*.php')
	->filter(fn($file) => strcasecmp($file->read(), 'Nette') === 0);

Filtrage en profondeur

Lors de la recherche récursive, vous pouvez définir la profondeur maximale de parcours à l'aide de la méthode limitDepth(). Si vous définissez limitDepth(1), seuls les premiers sous-répertoires sont parcourus, limitDepth(0) désactive le parcours en profondeur et la valeur –1 annule la limite.

Finder permet, à l'aide de fonctions personnalisées, de décider dans quel répertoire entrer lors du parcours. La fonction reçoit comme paramètre l'objet Nette\Utils\FileInfo et doit retourner true pour entrer dans le répertoire :

Finder::findFiles('*.php')
	->descentFilter($file->getBasename() !== 'temp');

Tri

Finder offre également plusieurs fonctions pour trier les résultats.

La méthode sortByName() trie les résultats par noms de fichiers. Le tri est naturel, c'est-à-dire qu'il gère correctement les nombres dans les noms et retourne par exemple foo1.txt avant foo10.txt.

Finder permet également de trier à l'aide d'une fonction personnalisée. Celle-ci reçoit comme paramètre deux objets Nette\Utils\FileInfo et doit retourner le résultat de la comparaison avec l'opérateur <=>, c'est-à-dire -1, 0 ou 1. Par exemple, de cette manière, nous trions les fichiers par taille :

$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());

Plusieurs recherches différentes

Si vous avez besoin de trouver plusieurs fichiers différents à différents emplacements ou répondant à d'autres critères, utilisez la méthode append(). Elle retourne un nouvel objet Finder, il est donc possible d'enchaîner les appels de méthodes :

($finder = new Finder) // nous stockons le premier Finder dans la variable $finder !
	->files('*.php')   // dans src/, nous recherchons les fichiers *.php
	->from('src')
	->append()
	->files('*.md')    // dans docs/, nous recherchons les fichiers *.md
	->from('docs')
	->append()
	->files('*.json'); // dans le dossier actuel, nous recherchons les fichiers *.json

Alternativement, il est possible d'utiliser la méthode append() pour ajouter un fichier spécifique (ou un tableau de fichiers). Elle retourne alors 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 dans les résultats de la recherche. Il s'agit d'une extension de la classe SplFileInfo, qui fournit des informations telles que la taille du fichier, la date de dernière modification, le nom, le chemin, etc.

De plus, elle fournit des méthodes pour retourner le chemin relatif, ce qui est utile lors du parcours en profondeur :

foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
	$absoluteFilePath = $file->getRealPath();
	$relativeFilePath = $file->getRelativePathname();
}

De plus, vous avez à disposition des méthodes pour lire et écrire le contenu du fichier :

foreach ($finder as $file) {
    $contents = $file->read();
    // ...
    $file->write($contents);
}

Retour des résultats sous forme de tableau

Comme on l'a vu dans les exemples, Finder implémente l'interface IteratorAggregate, vous pouvez donc utiliser foreach pour parcourir les résultats. Il est programmé de telle sorte que les résultats sont chargés uniquement pendant le parcours, donc si vous avez une grande quantité de fichiers, on n'attend pas que tous soient lus.

Vous pouvez également faire retourner les résultats sous forme de tableau d'objets Nette\Utils\FileInfo, et ce, par la méthode collect(). Le tableau n'est pas associatif, mais numérique.

$array = $finder->findFiles('*.php')->collect();
version: 4.0