Algunas veces nos toca la tarea de tener que trabajar con archivos en Drupal, y realmente suele ser algo tedioso especialmente si tenemos que descargarlos, ya que Drupal es tan grande y tiene tantas opciones que mucha gente se olvida que por debajo está Symfony y también podemos hacer uso de sus características, y este es el caso de la descarga de archivos.
Y es que Drupal utiliza Symfony aunque se nos olvide, y cuando trabajamos con controladores devolvemos un render array, pero después internamente Drupal transforma ese render array en un objeto Symfony Response. Pues podemos hacer uso directo de los objetos Response que Symfony nos provee para descargar archivos.
Opciones de descarga de Drupal
Si vamos a la documentación de Symfony veremos que hay una sección dedicada a la descarga de archivos, y esa precisamente es la que debemos usar.
Disponemos de dos opciones, una si el archivo no existe y otra en caso de que sí, ahora veremos un ejemplo de cómo descargar un archivo que existe y un video en el cual se ve el ejemplo y proceso completo.
En el ejemplo podéis ver como descargar el archivo y Media que haya adjuntos a un nodo, y a partir de ahí adaptarlo a vuestro código comprobando por ejemplo que el campo no esté vacío.
A continuación, veremos el código necesario para el proceso de descarga desde un Media relacionado con el nodo, primero lo básico y después un par de apuntes que nos pueden servir.
Descargando archivos desde un Media
El mayor problema que nos podemos encontrar en este caso es cargar desde el nodo el archivo final y es que tenemos a Media de por medio, veamos un poco de código sobre cómo hacerlo
/** @var \Drupal\node\NodeInterface $node */
$node = $this->entityTypeManager()->getStorage('node')->load(1);
$mid = $node->get('field_media')->target_id;
/** @var \Drupal\media\MediaInterface $media */
$media = $this->entityTypeManager()->getStorage('media')->load($mid);
En ese punto ya disponemos de la entidad Media en la variable $media, ahora carguemos el archivo:
$fid = $media->getSource()->getSourceFieldValue($media);
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager()->getStorage('file')->load($fid);
Y ya disponemos de la entidad file en la variable $file, la parte más compleja ya la tenemos, desde el nodo llegar a File, ahora tenemos que devolver el archivo en el controlador para que el navegador lo descargue, para ello vamos a utilizar la clase BinaryFileResponse como indica la documentación de Symfony:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
Return new BinaryFileResponse($file->getFileUri());
Y ya está, con eso podremos enviar el archivo desde el controlador, pero si lo probamos veremos que se nos está descargando y abriendo en el mismo instante, no se está descargando a nuestro PC, esto vamos a modificarlo añadiendo una opción a la respuesta:
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$response = new BinaryFileResponse($file->getFileUri());
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
);
return $response;
Añadiendo la opcion de DISPOSITION_ATTACHMENT indicaremos que se tiene que descargar, no abrirse en el mismo navegador.
Un problema extra, el tamaño
Podemos encontrarnos un problema adicional, y es que si el archivo a descargar es demasiado grande (un video, por ejemplo) tendremos problemas, de modo que es mejor devolverlo de otra manera, y es que existe la opción de “steam” para ello:
use Symfony\Component\HttpFoundation\File\Stream;
$stream = new Stream($file->getFileUri());
$response = new BinaryFileResponse($stream);
Si al BinaryFileResponse le enviamos como parámetro un objeto de Stream se encargará de enviar en trozos el archivo y el navegador lo descargará sin problemas. Personalmente os recomiendo que siempre utilicéis la opción de Stream para evitaros problemas.
Código final
Por último, os dejo el ejemplo completo del código en el controlador, aunque lo hemos ido viendo poco a poco es bastante útil verlo todo junto para hacerse una idea.
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/** @var \Drupal\node\NodeInterface $node */
$node = $this->entityTypeManager()->getStorage('node')->load(1);
$mid = $node->get('field_media')->target_id;
/** @var \Drupal\media\MediaInterface $media */
$media = $this->entityTypeManager()->getStorage('media')->load($mid);
$fid = $media->getSource()->getSourceFieldValue($media);
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager()->getStorage('file')->load($fid);
$stream = new Stream($file->getFileUri());
$response = new BinaryFileResponse($stream);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
);
return $response;
Y eso es todo, podéis adaptarlo a File en lugar de a Media de una manera muy sencilla (revisad el video, en el mismo se muestra cómo hacerlo).
Y poco más, eso es todo sobre como descargar un archivo, es más sencillo de lo que parece, pero hay que conocer un poco sobre Symfony y darse cuenta de que hace falta utilizarlo.
Me metí en la aventura de Drupal con la versión 6, y aquí estoy, 10 años después, escribiendo articulos y haciendo videos sobre Drupal, quien me lo iba a decir. Aunque he probado otros framworks y cms, me quedo con Drupal de lejos, pero Symfony y Django estan entre mis favoritos. Aficionado a la montaña, la bicicleta, y el comer, de eso que no falte.