9

I'm trying to download a file using the Slim 3 PHP Framework. Slim 2 was fairly straight forward, as I'm sure Slim 3 is too, but I just don't get it.

Any help would be appreciated. Based on the documentation here : http://www.slimframework.com/docs/objects/response.html I've added the package from here : https://github.com/guzzle/psr7

So my code at this point looks like :

$app->get('/worksheet/download/{filename}', function ($request, $response, $args) {

    error_log("__________________________");

    $fileName = $args['filename'];
    error_log($fileName);
    $newStream = new \GuzzleHttp\Psr7\LazyOpenStream($fileName, 'r');


    $newResponse = $response->withHeader('Content-type', 'application/octet-stream')
        ->withHeader('Content-Description', 'File Transfer')
        ->withHeader('Content-Disposition', 'attachment; filename=' . basename($fileName))
        ->withHeader('Content-Transfer-Encoding', 'binary')
        ->withHeader('Expires', '0')
        ->withHeader('Cache-Control', 'must-revalidate')
        ->withHeader('Pragma', 'public')
        ->withHeader('Content-Length', filesize($fileName))
        ->withBody($newStream);
return($newResponse);
});
alexw
  • 8,468
  • 6
  • 54
  • 86
Jim_M
  • 273
  • 1
  • 2
  • 10
  • It should be noted that the File that I'm trying to download is outside the document root. Otherwise I'd just do a straightforward link to the file. – Jim_M Mar 15 '16 at 14:49

3 Answers3

12

I'm using readfile method from php:

$file = 'monkey.gif';

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment;filename="'.basename($file).'"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    readfile($file);
    exit;
}

This example is from the link above, maybe you can try to implement theses headers with the Slim Response object and WithHeader method.

The diference between {filename} and [{filename}] is that second form, filename is optional.

$app->get('/my[/{optional}]/route', function($req, $res, $args) {});
$app->get('/myother/{not_optional}/route', function($req, $res, $args) {});

In this example, you can access to /my/route because argument is not required but you can't access to /myother/route because you NEED not_optional argument.
There are more info in Slim documentation.

EDIT

$app->get('/download', function($req, $res, $args) {
    $file = 'public_html/images/back2.jpg';
    $response = $res->withHeader('Content-Description', 'File Transfer')
   ->withHeader('Content-Type', 'application/octet-stream')
   ->withHeader('Content-Disposition', 'attachment;filename="'.basename($file).'"')
   ->withHeader('Expires', '0')
   ->withHeader('Cache-Control', 'must-revalidate')
   ->withHeader('Pragma', 'public')
   ->withHeader('Content-Length', filesize($file));

readfile($file);
return $response;
});

This code works fine for me and it's made with Slim 3.3.0

legomolina
  • 1,043
  • 2
  • 13
  • 33
  • legomolina, are you using this code inside the SLIM 3 framework ? Also, thanks for the heads up on the Required Vs. Optional. – Jim_M Mar 15 '16 at 14:38
  • No, few times I need to implement this code but when I did I was out of Slim. I think it should work fine with Slim – legomolina Mar 15 '16 at 21:45
  • 3
    With large files you end up hitting the memory limit of PHP because Slim wraps a routed method with `ob_get_clean`. – carbontwelve May 05 '16 at 14:23
8
$app->get('/test-download', function($request, Slim\Http\Response $response, $args) {
    $file = __DIR__ . '/2.csv';
    $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
});
bad4iz
  • 220
  • 2
  • 4
  • 1
    Thanks. Do you have a suggestion about closing the file pointer? – Gabriel Dec 12 '18 at 13:27
  • try { //the code above from $file= to the return statement } catch(\Exception $e) { $this->logger->error("Error while sending file $file",["exception"=>$e]); } finally { $stream->close(); } – Thomas May 27 '19 at 19:14
0

This is the block of code I used for my slim 3 projects :

 $file = "provide your full file path";
    
            $ext = pathinfo($file, PATHINFO_EXTENSION);
            $mimetype = get_mime_type($ext);
            header("Content-Type: $mimetype");
            header("Content-Transfer-Encoding: Binary"); 
            header("Content-Disposition: attachment; filename=\"".basename($file) . "\"");
            header("Expires: 0");
            header("Cache-Control: must-revalidate");
            // load file
            readfile( __DIR__ . '/../..'.$file);
            ob_end_flush();

This is the function for finding mimetype of the file:

function get_mime_type($ext)
{
    $mime_types = array(

        'txt' => 'text/plain',
        'htm' => 'text/html',
        'html' => 'text/html',
        'php' => 'text/html',
        'css' => 'text/css',
        'js' => 'application/javascript',
        'json' => 'application/json',
        'xml' => 'application/xml',
        'swf' => 'application/x-shockwave-flash',
        'flv' => 'video/x-flv',

        // images
        'png' => 'image/png',
        'jpe' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'gif' => 'image/gif',
        'bmp' => 'image/bmp',
        'ico' => 'image/vnd.microsoft.icon',
        'tiff' => 'image/tiff',
        'tif' => 'image/tiff',
        'svg' => 'image/svg+xml',
        'svgz' => 'image/svg+xml',

        // archives
        'zip' => 'application/zip',
        'rar' => 'application/x-rar-compressed',
        'exe' => 'application/x-msdownload',
        'msi' => 'application/x-msdownload',
        'cab' => 'application/vnd.ms-cab-compressed',

        // audio/video
        'mp3' => 'audio/mpeg',
        'qt' => 'video/quicktime',
        'mov' => 'video/quicktime',

        // adobe
        'pdf' => 'application/pdf',
        'psd' => 'image/vnd.adobe.photoshop',
        'ai' => 'application/postscript',
        'eps' => 'application/postscript',
        'ps' => 'application/postscript',

        // ms office
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'doc' => 'application/msword',
        'dot' => 'application/msword',
        'dotx' => 'application/msword',
        'rtf' => 'application/rtf',
        'xls' => 'application/vnd.ms-excel',
        'ppt' => 'application/vnd.ms-powerpoint',

        // open office
        'odt' => 'application/vnd.oasis.opendocument.text',
        'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
    );
   
    if (array_key_exists($ext, $mime_types)) {
        return $mime_types[$ext];
    }
}
Shinoy p b
  • 353
  • 3
  • 13