Drupal 8/9 utilizes the most popular components of Symfony like HttpKernel, HttpFoundation, EventDispatcher, DependencyInjection, Routing, and many more. Today my interest is in the HttpFoundation component which includes classes for Request and Response and those classes are used by Drupal for sending Response from the system and converting the request into the usable Request object.
Okay, Let’s come to the point.
I have developed several projects in Drupal and a few of them have the functionality of downloading some kind of CSV file. CSV file contains heavy mathematical calculations and so I was unable to utilize View Csv Downloader and any other contributed module for this.
I was using the most traditional way which we used in PHP for generating CSV, But it’s not a standard way in Drupal. So now we prepare a CsvFileResponse by extending the Response class of HttpFoundation.
Drupal Response class is not an abstract class and neither does it’s implementing any interface so we will override only those functions which we need for our Response class and will leave others.
- We will override the constructor of the class which is calling the required function.
- We will override the setContent function to prepare CSV.
Here we go.
<?php
declare(strict_types = 1);
namespace Drupal\sc\HttpFoundation;
use League\Csv\Writer;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/**
* Provides a CSV file response object.
*
* This object consumes an associative array of data and transforms it to a CSV
* string. The correct headers are applied to the response to allow the file to
* automatically download.
*/
class CsvFileResponse extends Response {
/**
* The filename that is downloaded.
*
* @var string
*/
protected $filename;
/**
* Constructor for CsvFileResponse.
*
* @param array $content
* The associative array representing rows in the CSV file.
* @param string $filename
* The filename that is downloaded.
* @param int $status
* The HTTP status code of the response.
* @param array $headers
* The response headers. These are merged with default headers for the CSV
* file to be downloaded.
*/
public function __construct(
array $content,
string $filename = 'export.csv',
int $status = Response::HTTP_OK,
array $headers = []
) {
$this->setContent($content);
$this->setFilename($filename);
$this->setStatusCode($status);
$headers = array_merge($this->getDefaultHeaders(), $headers);
$this->headers = new ResponseHeaderBag($headers);
$this->setProtocolVersion('1.0');
}
/**
* {@inheritdoc}
*/
public function setContent($content): self {
if (!is_array($content)) {
throw new \InvalidArgumentException(
'Content must be an array to be converted into a CSV file.'
);
}
$writer = Writer::createFromPath('php://memory', 'w');
$writer->insertAll($content);
$this->content = $writer->getContent();
// Remove the writer from memory.
unset($writer);
return $this;
}
/**
* Get the filename.
*
* @return string
* The filename that is downloaded.
*/
public function getFilename(): string {
return $this->filename;
}
/**
* Set the filename.
*
* @param string $filename
* The filename that is downloaded.
*
* @return self
* Returns itself for a fluid interface.
*/
public function setFilename(string $filename): self {
$this->filename = $filename;
return $this;
}
/**
* Get the default response headers.
*
* @return array
* The default response headers.
*/
protected function getDefaultHeaders(): array {
return [
'Content-Type' => 'text/csv',
'Content-Transfer-Encoding' => 'binary',
'Content-Description' => 'File Transfer',
'Content-Disposition' => sprintf(
'attachment; filename=%s;',
$this->getFilename()
),
];
}
}
I hope this will help you in some way, I will keep revising this article to make it better.