Introduction : « Erreur fatale » et 2 gigaoctets de logs
Imaginez : vous êtes développeur, et un administrateur système arrive avec une clé USB. Dessus se trouve un fichier de log du serveur web des six derniers mois. La taille ? Un modeste 2,5 gigaoctets. « Il faut trouver toutes les adresses IP à partir desquelles des tentatives de brute force ont été faites », dit-il avant de partir boire un café.
Vous ouvrez votre IDE et écrivez machinalement :
$lines = file('huge_log.txt');foreach ($lines as $line) { // on parse...}Vous cliquez sur « Exécuter »... et vous voyez un écran blanc dans le navigateur, et dans les logs d'erreurs — Fatal Error: Allowed memory size exhausted. Familier ? C'est le piège classique dans lequel presque tous les développeurs PHP sont tombés.
La question « How to read a large file line by line? » sur Stack Overflow a rassemblé des centaines de milliers de vues pour une bonne raison. C'est l'un de ces problèmes où la solution « naïve » tue le serveur, tandis que la bonne approche sauve le projet. Aujourd'hui, nous allons voir comment lire d'énormes fichiers en PHP sans dépasser la limite de mémoire tout en maintenant des performances élevées. C'est parti !
Pourquoi file() et file_get_contents() sont le mal pour les gros volumes de données
Dissipons tout de suite le principal mythe. Les fonctions file() et file_get_contents() sont des fonctions gourmandes. Elles chargent l'intégralité du fichier en mémoire vive. Si votre fichier pèse 2 Go et que la limite memory_limit dans php.ini est fixée à 128 Mo (valeur standard pour de nombreux hébergements), vous obtiendrez inévitablement une erreur.
De plus, PHP stocke les chaînes avec un encodage, ce qui peut multiplier la consommation mémoire par 2 à 4 par rapport à la taille du fichier sur le disque. Autrement dit, 2 Go de logs peuvent « engloutir » 6 à 8 Go de RAM !
Conclusion : si la taille du fichier dépasse 10 à 20 % de votre memory_limit, oubliez file(). Nous avons besoin d'une approche par flux.
La bonne méthode : fgets() et la lecture par flux
La méthode la plus fiable et la plus performante consiste à utiliser fopen() avec fgets(). Ces fonctions ne lisent pas le fichier en entier, mais une ligne à la fois. Imaginez que vous buvez un jus avec une paille — vous n'avalez pas tout le verre d'une seule gorgée, mais vous sirotez petit à petit. C'est exactement ainsi que fonctionne la lecture par flux.
Voici un exemple basique mais fonctionnel :
<?php$handle = fopen('huge_log.txt', 'r');if ($handle) { while (($line = fgets($handle)) !== false) { // Traite une seule ligne echo $line; } fclose($handle);}?>Que se passe-t-il ici ? fopen() ouvre le fichier et retourne un pointeur (resource). fgets() lit à chaque fois une ligne jusqu'au caractère de nouvelle ligne (). La mémoire n'est allouée que pour la ligne courante, pas pour le fichier entier. Après le traitement, la ligne est détruite par le ramasse-miettes (ou écrasée).
Points importants sur les performances
- Taille du tampon :
fgets()lit par défaut jusqu'à 8192 octets à la fois. Si les lignes sont plus longues, la fonction effectuera plusieurs itérations. Pour la grande majorité des logs, cela suffit. - Vérification de false : Utilisez toujours la comparaison stricte
!== false. Pourquoi ? Parce qu'une ligne peut être vide ("") ou contenir "0", etwhile ($line = fgets($handle))pourrait s'arrêter prématurément. - Fermeture du fichier : Appelez toujours
fclose(). Oui, PHP le fera automatiquement à la fin du script, mais si vous traitez un fichier dans une boucle ou dans un script d'arrière-plan, les descripteurs non fermés constituent une fuite de ressources.
Super-charge : Les générateurs (yield) pour la réutilisation
La gestion manuelle de fopen/fclose est bien, mais si vous devez lire différents fichiers dans différentes parties du projet, le code commencera à se dupliquer. Les générateurs viennent à la rescousse — l'une des fonctionnalités les plus élégantes de PHP.
Un générateur permet de créer une fonction, qu