A common requirement of web-based applications is to upload and download files. But, out of the box, there’s no simple way to download them in Zend Expressive. This tutorial shows you how - step-by-step.
A common requirement of web-based applications is to upload and download files. But, out of the box, there’s no simple way to download them in Zend Expressive. This tutorial shows you how - step-by-step.
Recently, I was asked on Twitter by @dgoosens, about how to download files using Zend Expressive.
The timing was pretty good, as I’d done a simple implementation in a recent Zend Expressive project. So I knocked up a quick example and he, @acelayaa, and I talked it over, making various changes and suggestions along the way.
So, In today’s tutorial, I’m going to walk through a 3-step process for downloading files when using Zend Expressive.
1. The Download Functionality
Gladly, there’s not a lot involved in doing so. All you really need to do is make use of a Diactoros Stream, and some of PHP’s in-built functionality. Have a look at the function below, and then let’s step through what’s going on.
protected function downloadFile(ResponseInterface $response, $file)
{
$body = new Stream($file);
return $response
->withHeader('Content-Type', (new \finfo(FILEINFO_MIME))->file($file))
->withHeader('Content-Disposition', 'attachment; filename=' . basename($file))
->withHeader('Content-Transfer-Encoding', 'Binary')
->withHeader('Content-Description', 'File Transfer')
->withHeader('Pragma', 'public')
->withHeader('Expires', '0')
->withHeader('Cache-Control', 'must-revalidate')
->withBody($body)
->withHeader('Content-Length', "{$body->getSize()}");
}
The method can be placed in any Zend Expressive PageAction
class, so it will be able to be passed a response object. $file
can be taken from whatever source works for you; whether that’s a form or some other kind of (sanitized) user input. For the purposes of this example, I’m hard-coding it.
After that, I’ve created a variable, called $body
, which contains the body of the response. This is a new Zend\Diactoros\Stream
object, which reads in the contents of the file which $file
points to.
With that done, I’m returning a new response object, after setting all of the headers necessary to download a file, and a few for elementary cache control.
The original gist I shared specified a generic ‘Content-Type’ value of application/octet-stream
. But I wanted to put a bit more smarts in to this example. Given that, I made use of finfo, available in PHP since version 5.3.
This class is a quick and simple, not to mention memory saving, way of getting the mime-type data from a file, without reading it in to memory.
Together, the Content-Disposition, Content-Transfer-Encoding, and Content-Description headers tell the client that the file is going to be downloaded. If I’d removed the Content-Disposition header the file would be rendered in the client, if at all possible.
2. Using the Download Method
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return $this->downloadFile($response, '/path/to/an/existing/file');
}
With the method complete, here’s how to make use of it. Create a project, based off of the Zend Expressive Skeleton Installer. Once it’s done you’ll find HomePageAction.php
under /src/App/Action
.
There, replace the original definition of __invoke, with the one above, and provide the path to a valid file on your filesystem.
3. Running the Application & Downloading the File
With that done, in your terminal, from the root directory of the project, call composer serve
. This will launch the application where it will listen on port 8080, on your local machine.
Now that the application’s ready to accept requests, in your browser navigate to http://localhost:8080/
. When the request finishes, you’ll see the file download or ask you for where to save the download, depending on the browser you’re using.
Conclusion
Zend Expressive does a great job of making application development a breeze. But out of the box, it doesn’t provide a specific way of how to download a file.
This tutorial has shown an elementary implementation that will do it. There are a lot of extras that could be added, to increase security, defensiveness, and functionality. But, as a teaching example, it does the job.
Join the discussion
comments powered by Disqus