Introducción: cuando un simple file_get_contents mata tu servidor
\nImagina esta situación: estás escribiendo un analizador de logs, un importador de CSV con millones de filas o procesando un volcado de base de datos. Escribes $data = file_get_contents('bigfile.log'); — y tu script PHP falla con el error Fatal error: Allowed memory size exhausted. ¿Te suena? Si no, has tenido suerte. Pero tarde o temprano, todo desarrollador se enfrenta a la pregunta: «¿Cómo leer un archivo grande línea por línea sin matar el servidor?».
En Stack Overflow, la pregunta «How to read a large file line by line?» tiene cientos de miles de visitas. Y no es casualidad. En este artículo no solo veremos un método, sino varios enfoques, desde el básico hasta el avanzado. Aprenderás a leer archivos de decenas de gigabytes usando solo un par de megabytes de RAM. ¡Allá vamos!
\n\nPor qué file_get_contents y file() son el enemigo de los archivos grandes
\nAntes de sumergirnos en la optimización, entendamos por qué las funciones estándar no sirven.
\n- \n
- file_get_contents() — carga el archivo completo en una cadena. Si el archivo pesa 2 GB, PHP intentará asignar 2 GB de RAM. En un hosting con un límite memory_limit de 128 MB, esto es un error fatal asegurado. \n
- file() — también lee todo el archivo, pero además lo divide en un array de líneas. El consumo de memoria aumenta aún más (más la sobrecarga del array). \n
Conclusión: si el archivo supera el 10-20% de tu memory_limit, olvídate de estas funciones. Necesitamos un enfoque basado en flujo.
\n\nMétodo 1: El clásico — fgets() y el manejador de archivo
\nLa forma más básica y confiable es abrir el archivo con fopen() y leerlo línea por línea con fgets(). Este método usa el búfer interno de PHP, que normalmente es de 8 KB, y no carga todo el archivo en memoria.
<?php\n$handle = fopen('bigfile.log', 'r');\nif ($handle) {\n while (($line = fgets($handle)) !== false) {\n // Procesamos la línea\n echo $line; // o la guardamos en BD, o contamos algo\n }\n fclose($handle);\n} else {\n // Error al abrir el archivo\n echo "No se pudo abrir el archivo.";\n}\n?>\n\nVentajas: consumo mínimo de memoria, funciona en cualquier versión de PHP.
\nDesventajas: hay que cerrar el archivo explícitamente, no tiene métodos cómodos para saltar encabezados o iterar con conteo de líneas.
\nEste código soportará archivos de cualquier tamaño, siempre que no intentes guardar todas las líneas en un array dentro del bucle.
\n\nMétodo 2: SplFileObject — un enfoque orientado a objetos
\nDesde PHP 5.1, tenemos la fantástica clase SplFileObject. Implementa las interfaces Iterator y SeekableIterator, lo que la hace ideal para leer línea por línea.
<?php\n$file = new SplFileObject('bigfile.log');\nwhile (!$file->eof()) {\n $line = $file->fgets();\n // Procesamos la línea\n echo $line;\n}\n?>\n\n¡Pero ojo! SplFileObject por defecto también puede cargar líneas en memoria si usas current() dentro de un foreach. Para garantizar un bajo consumo, usa fgets() en un bucle while, como se muestra arriba.
Una versión más avanzada es ajustar las banderas para optimizar:
\n\n<?php\n$file = new SplFileObject('bigfile.log');\n$file->setFlags(SplFileObject::DROP_NEW_LINE); // Quitamos el \\n de la línea\n\nforeach ($file as $lineNum => $line) {\n // $lineNum es el número de línea (empezando desde 0)\n echo "Línea $lineNum: $line\\n";\n}\n?>\n\nImportante: Al usar foreach con SplFileObject, PHP igual lee una línea a la vez. La memoria no se ve afectada. Pero si intentas llamar a $file->seek(1000000) y luego saltar por el archivo, ten cuidado: seek recorre desde el principio hasta la línea indicada, lo que puede ser lento en archivos enormes.