Laravel-medialibrary v7 preview: multi file downloads
laravel-medialibrary is a powerhouse package that can help handle media in a Laravel application. It can organise your files across multiple filesystems, generate thumbnails, optimize images and much much more.
My team and I are hard at work creating a new major version, v7, that adds a lot of awesome features such as responsive images, vue components to assist with uploading, single file collections, ... In this post I'd like to highlight one cool handy feature that's coming to v7: multi file downloads.
In our package every file is associated with a Media
model. To download a single file you can do this in a controller:
public function download(Media $media)
{
return response()->download($media->getPath());
}
Because Media
implements Laravel's Responsable interface you can also write that a bit shorter:
public function download(Media $media)
{
return $media;
}
Pretty sweet! But what if you want to download multiple files at once? Currently the medialibrary won't help you with this and you need to take care of this yourself. You could generate a zip file with all media you want to download on your server and then do
return response()->download($pathToZippedMediaFiles);
But maybe you want to download big files, in that case creating the zip takes a while. Or maybe the files are stored on a remote service like S3, so now it takes even longer because the files need to be copied over first.
I'm happy to share that medialibrary v7 will solve this for you. It includes a ZipStreamResponse
class that allows you to respond with a stream. Files will be zipped on the fly and you can even include files from multiple filesystems.
Let's take a look at an example on how to use ZipStreamResponse
. We're going to create two routes. Visiting add-files
will add some files to our medialibrary, visiting download-files
will download them. I'm using routes here for demonstration purposes, in a real world app you'd probably use ZipStreamResponse
in a controller.
Route::get('add-files', function() {
//create a regular model
$article = Article::create();
// add a file to the downloads collection on the local disk
$article
->addMedia($pathToAFile)
->toMediaCollection('downloads');
// add a file to the downloads collection on the s3 disk
$article
->addMedia($pathToABigFile)
->toMediaCollection('downloads', 's3');
return 'files added!';
});
Route::get('download-files', function() {
//get all files in the download collection
$allMedia = Article::first()->getMedia('downloads');
// download them in a streamed way, so no prob if your files are very large
return ZipStreamResponse::create('my-files.zip')->addMedia($allMedia);
});
That last line is the most important one. It's kinda cool that the zip is created on the fly and that it pulls data from both the local disk and s3.
Coding ZipStreamResponse
up was easier than I thought it would be. The maennchen/zipstream-php does the hard work of creating a zip stream. All I need to do was to integrate the provided ZipStream
class in our medialibrary. Here's the entire source code of Spatie\MediaLibrary\ZipStreamResponse
:
namespace Spatie\MediaLibrary;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Contracts\Support\Responsable;
use Spatie\MediaLibrary\Media;
use Symfony\Component\HttpFoundation\StreamedResponse;
use ZipStream\ZipStream;
class ZipStreamResponse implements Responsable
{
/** string */
protected $zipName;
/** Illuminate\Support\Collection */
protected $mediaItems;
public static function create(string $zipName)
{
return new static($zipName);
}
public function __construct(string $zipName)
{
$this->zipName = $zipName;
}
public function addMedia($mediaItems)
{
$this->mediaItems = $mediaItems;
return $this;
}
public function toResponse($request)
{
return new StreamedZipResponse(function () {
$zip = new ZipStream($this->zipName);
$this->mediaItems->each(function (Media $media) use ($zip) {
$zip->addFileFromStream($media->file_name, $media->stream());
});
$zip->finish();
});
}
}
Of course there are situations (eg. when the same assets get downloaded over and over again, or when download speed is very important) where you still want to create a zip file locally and store it for later use. But I believe StreamedZipResponse
does provide a good solution for the proverbial 80%
Like mentioned above, multi file downloads are coming to v7 of laravel-medialibrary which will be released around February - March 2018. Of course you can start using v6 of laravel-medialibrary right now.
What are your thoughts on "Laravel-medialibrary v7 preview: multi file downloads"?