The 3-Step Guide to Downloading Files in Zend Expressive

How to Download Files with Zend Expressive

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')
        ->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.


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.

About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.