Devolver la descarga de un fichero en Laravel
Uso de `response()→download()` para devolver la descarga de un fichero en Laravel, y la posibilidad de eliminarlo tras su envío.
Ayer me estuve peleando con la generación de ficheros .zip y su posterior descarga desde Laravel. La idea era que el cliente, en Filament:
- Seleccionara los datos que quería exportar
- Pulsara el botón "Exportar"
- Se generara un fichero .zip
- Se comience la descarga en su navegador
El código de generación del .zip era algo parecido al de este post. Sin embargo, quería asegurarme de que el fichero generado no se quede ocupando espacio en el dispositivo y que se fueran eliminando periódicamente.
Para ello, en primer lugar, decidí usar ficheros temporales. Esto me iba a permitir crear ficheros en una ruta temporal y el sistema los iría eliminando cuando considere. Por defecto, todos los ficheros en las rutas temporales en Linux viven 10 días (ficheros en la ruta /tmp
).
Si en esos 10 días el cliente exportara mucha información es posible que el sistema se fuera quedando sin espacio, por lo que es una buena práctica borrar el fichero después de utilizarlo. El código quedaría así:
public function export(Collection $records)
{
// generar ruta de un nuevo fichero temporal
$path = tempnam(sys_get_temp_dir(), 'data').'.zip';
// generamos el fichero .zip en esa ruta
$zip = $this->generateZipForRecords($records, path: $path);
return response()->download($path, 'export.zip');
}
Este último return response()→download($path, 'export.zip');
devuelve al navegador el fichero listo para su descarga. Sin embargo, como la función recibe el path de un fichero local para poder descargarlo, ¡no puedo eliminarlo!
Aquí ya se me empezaron a ocurrir ideas locas de utilizar un cron que buscara esos ficheros y los eliminara pero, por suerte, Laravel tenía una solución muy elegante:
return response()
->download($path, 'qrcodes.zip')
->deleteFileAfterSend(true);
La llamada al método deleteFileAfterSend(true)
hace que Laravel automáticamente elimine el fichero localizado en $path
tras enviar todos sus bytes al navegador, quedando así nuestro sistema intacto :-)