Cuando un fichero es demasiado grande para cargarlo todo en memoria con file_get_contents(), PHP ofrece las funciones de streams: fopen() para abrir el fichero y obtener un recurso, fgets() y fread() para leer en trozos, fwrite() para escribir, y fclose() para cerrar el recurso y liberar memoria.
Modos de apertura de fopen()
Modo |
Descripción |
| Solo lectura. El puntero al inicio. |
| Lectura y escritura. El puntero al inicio. |
| Solo escritura. Trunca el fichero a cero. Crea si no existe. |
| Lectura y escritura. Trunca a cero. Crea si no existe. |
| Solo escritura. El puntero al final (append). Crea si no existe. |
| Lectura y escritura. Append. Crea si no existe. |
| Solo escritura. Falla si el fichero ya existe. |
| Lectura y escritura. Falla si ya existe. |
| Escritura sin truncar. El puntero al inicio. Crea si no existe. |
Leer un CSV grande línea a línea con fgets()
<?php
$ruta = '/var/datos/pedidos-2026.csv';
$fh = fopen($ruta, 'r');
if ($fh === false) {
throw new RuntimeException("No se pudo abrir: $ruta");
}
$cabecera = fgetcsv($fh); // ['id', 'cliente', 'total', 'fecha']
$procesados = 0;
while (($fila = fgetcsv($fh)) !== false) {
[$id, $cliente, $total, $fecha] = $fila;
// procesar cada fila sin cargar el fichero completo en memoria
$procesados++;
}
fclose($fh);
echo "$procesados pedidos procesadosn";
?>
Leer un log línea a línea con fgets()
<?php
$fh = fopen('/var/log/nginx/access.log', 'r');
if ($fh === false) {
exit('No se pudo abrir el log');
}
$errores = 0;
while (!feof($fh)) {
$linea = fgets($fh);
if ($linea === false) break; // feof() a veces llega tarde
if (str_contains($linea, ' 500 ')) {
$errores++;
}
}
fclose($fh);
echo "Errores 500 encontrados: $erroresn";
?>
Leer en chunks con fread()
fread() lee exactamente N bytes del stream, ideal para ficheros binarios:
<?php
$fh = fopen('/var/datos/imagen.bin', 'rb'); // 'b' para binario en Windows
if ($fh === false) exit('Error');
$CHUNK = 8192; // 8 KB
while (!feof($fh)) {
$trozo = fread($fh, $CHUNK);
if ($trozo === false) break;
// procesar o enviar $trozo...
echo $trozo;
}
fclose($fh);
?>
Escribir con fwrite()
<?php
$fh = fopen('/var/log/app/proceso.log', 'a');
if ($fh === false) {
throw new RuntimeException('No se pudo abrir el log para escritura');
}
$linea = date('[Y-m-d H:i:s]') . " Proceso completado: 1000 registrosn";
$bytes = fwrite($fh, $linea);
if ($bytes === false) {
fclose($fh);
throw new RuntimeException('Error al escribir en el log');
}
fclose($fh);
?>
flock(): escritura concurrente segura
Cuando varios procesos pueden escribir en el mismo fichero simultáneamente, flock() evita la corrupción:
<?php
function escrituraConcurrente(string $ruta, string $datos): void
{
$fh = fopen($ruta, 'a');
if ($fh === false) {
throw new RuntimeException("No se pudo abrir $ruta");
}
// Bloqueo exclusivo de escritura (espera hasta obtenerlo)
if (flock($fh, LOCK_EX)) {
fwrite($fh, $datos);
flock($fh, LOCK_UN); // liberar el bloqueo
}
fclose($fh);
}
escrituraConcurrente('/tmp/contadores.txt', "proceso-" . getmypid() . "n");
?>
Errores habituales
- Olvidar fclose(): PHP libera los recursos al final del script, pero en scripts de larga duración o crons que procesan muchos ficheros, no cerrar explícitamente agota los descriptores de fichero del proceso.
- feof() prematuro:
feof()devuelvetruesolo después de un intento de lectura fallido, no antes. Por eso el patrón correcto es comprobarfgets() !== false, no solo!feof(). - Confundir
fgetcsv()confgets():fgets()devuelve la línea raw;fgetcsv()la parsea como CSV y devuelve un array.
La documentación de fopen() cubre todos los modos, los wrappers de stream (php://, compress.gz://, etc.) y el uso con contextos HTTP.
