Streaming large file downloads in Laravel.

styxofdynamite

Luke

Posted on December 13, 2019

Streaming large file downloads in Laravel.

Background

In my role as a developer at Reviews.io we often have customers who want to export large datasets from our Dashboard. Large dynamic file downloads can be problematic as they introduce the scope for memory exhaustion.

One way in which we tackle this issue is by streaming our file downloads using Laravel's Response Streaming.

Streamed Downloads

Sometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the streamDownload method in this scenario. This method accepts a callback, file name, and an optional array of headers as its arguments

The code

Let's start by declaring a route to get our download file from, I've opted to make the file retrievable by making a get request to /download with an optional number of rows we want the file to contain.

<?php

Route::get('download/{rows?}', ['uses' => 'StreamedDownloadController@download']);

Enter fullscreen mode Exit fullscreen mode

Now we need to implement our Controller Method download

N.B. I'm using Faker here to generate the data, in reality this would be fetched from a database but the idea is essentially the same.

First of we need to define the method on our controller.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Faker;

class StreamedDownloadController extends Controller
{
    public function download($rows = 50000)
    {
Enter fullscreen mode Exit fullscreen mode

We've specified a default value for the number of rows we want to write out to the file.

        // callback function that writes to php://output
        $callback = function() use ($rows) {

        // Open output stream
        $handle = fopen('php://output', 'w');

        // Add CSV headers
        fputcsv($handle, [
            'Name',
            'Address', 
        ]);

        // Generate a faker instance
        $faker = Faker\Factory::create();

        // add the given number of rows to the file.
        for ($i=0; $i < $rows ; $i++) { 
            $row = [
                $faker->name,
                $faker->address,
            ];
            fputcsv($handle, $row);
        }


        // Close the output stream
        fclose($handle);
    };
Enter fullscreen mode Exit fullscreen mode

This is the callback method that will process writing the file out to the php:output buffer.

Next up we specify the Content-Type so that the browser knows what format the file is supposed to be in.

    // build response headers so file downloads.
    $headers = [
        'Content-Type' => 'text/csv',
    ];
Enter fullscreen mode Exit fullscreen mode

And finally we return a streamDownload() response, that executes the callback, writing the generated file out.

    // return the response as a streamed response.
    return response()->streamDownload($callback, 'download.csv', $headers);
    }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
styxofdynamite
Luke

Posted on December 13, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related