fopen(), fread(), fwrite() y fclose() en PHP: streams para ficheros grandes

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

r

Solo lectura. El puntero al inicio.

r+

Lectura y escritura. El puntero al inicio.

w

Solo escritura. Trunca el fichero a cero. Crea si no existe.

w+

Lectura y escritura. Trunca a cero. Crea si no existe.

a

Solo escritura. El puntero al final (append). Crea si no existe.

a+

Lectura y escritura. Append. Crea si no existe.

x

Solo escritura. Falla si el fichero ya existe.

x+

Lectura y escritura. Falla si ya existe.

c

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() devuelve true solo después de un intento de lectura fallido, no antes. Por eso el patrón correcto es comprobar fgets() !== false, no solo !feof().
  • Confundir fgetcsv() con fgets(): 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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP