Slim 3 Download Files

I need to implement a routine to download files from the browser, however the files are not downloaded they are rendered in the browser.

public function getDownloadAction($request, $response, $args)
    {
        $file =  ROOT_PATH . '/cache/files/amtlib.dll';
        $response->withHeader('Content-Type', 'application/force-download')
            ->withHeader('Content-Type', 'application/octet-stream')
            ->withHeader('Content-Type', 'application/download')
            ->withHeader('Content-Description', 'File Transfer')
            ->withHeader('Content-Transfer-Encoding', 'binary')
            ->withHeader('Content-Disposition', 'attachment; filename="'.basename($file).'"')
            ->withHeader('Expires', '0')
            ->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
            ->withHeader('Pragma', 'public')
            ->withHeader('Content-Length', filesize($file));

        readfile($file);
        return $response;
    }

The data needs to written to the body of the response object. You can use the built-in Slim Stream class together with fopen to to this for you:

    $app->get('/test-download', function($request, Slim\Http\Response $response, $args) {
        $file = __DIR__ . '/test.html';
        $fh = fopen($file, 'rb');

        $stream = new \Slim\Http\Stream($fh); // create a stream instance for the response body

        return $response->withHeader('Content-Type', 'application/force-download')
                        ->withHeader('Content-Type', 'application/octet-stream')
                        ->withHeader('Content-Type', 'application/download')
                        ->withHeader('Content-Description', 'File Transfer')
                        ->withHeader('Content-Transfer-Encoding', 'binary')
                        ->withHeader('Content-Disposition', 'attachment; filename="' . basename($file) . '"')
                        ->withHeader('Expires', '0')
                        ->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
                        ->withHeader('Pragma', 'public')
                        ->withBody($stream); // all stream contents will be sent to the response
    });

The Content-Length header will be automatically appended.

The code also worked for me with one Content-Type header, and without the Content-Transfer-Encoding header. So you may be able to simplify this by leaving headers out.

1 Like

I struggled with this in Slim 3.0. Do NOT make the mistake I made.
I was trying something like the following and the pdf binary was getting displayed on the screen.

$header['Content-Description']  = 'File Transfer';
$header['Content-Disposition'] = 'attachment; filename="' .basename("$file") . '"';

$response   = $response->withHeader($header);

// It did NOT work.
// In order to make it work, you need to set the key/value pairs using $response->withHeader() just like it's described above. And it works like a charm. For example,
    $response   = $response->withHeader('Content-Type', 'application/pdf');
    $response   = $response->withHeader('Content-Description', 'File Transfer');
    $response   = $response->withHeader('Content-Disposition', 'attachment; filename="' .basename("$file") . '"');
    $response   = $response->withHeader('Content-Transfer-Encoding', 'binary');
    $response   = $response->withHeader('Expires', '0');
    $response   = $response->withHeader('Cache-Control', 'must-revalidate');
    $response   = $response->withHeader('Pragma', 'public');
    $response   = $response->withHeader('Content-Length', filesize($file));

Hi Guys,

I’m trying to implement the solution presented here but my scenario is an auto generate csv from the DB. I’m keep getting this error.

Uncaught exception ‘RuntimeException’ with message ‘Could not read from stream’ in {…}/vendor/slim/slim/Slim/Http/Stream.php:389

Already look for permissions in the file and possible encoding bugs that could hurt the final stream. Couldn’t find anything. I’m using the 3.9.2 version.

Hello @rhamses,

Can you provide a minimal code example that triggers this exception?