★ laravel-medialibrary v7 has been released
Tags: php, laravel, opensource
Published: 2020-01-02 22:59:46

---
title: ★ laravel-medialibrary v7 has been released
published: true
date: 2018-03-19 17:25:56 UTC
tags: php, laravel, opensource
canonical_url: https://freek.dev/1008-laravel-medialibrary-v7-has-been-released
---

[laravel-medialibrary](https://docs.spatie.be/laravel-medialibrary/) is a powerful package that can help handle media in a Laravel application. It can [organise your files across multiple filesystems](https://docs.spatie.be/laravel-medialibrary/v6/advanced-usage/working-with-multiple-filesystems), [generate thumbnails](https://docs.spatie.be/laravel-medialibrary/v6/converting-images/defining-conversions), [optimize images](https://docs.spatie.be/laravel-medialibrary/v6/converting-images/optimizing-converted-images) and much much more. At [Spatie](https://spatie.be) we use this package in nearly every project.

The last few months our team has been hard at work to create a new major version of the package. I'm happy to share that this week I [released the new v7 on stage](https://twitter.com/BitFumes/status/974949452053209093) at the [Laravel Live conference](http://laravellive.in/) in New Delhi. In this blogpost I'd like to show you some of the new features.

## General introduction #

To get a feeling of the basic features of the medialibrary, you should watch this video.

<iframe width="640" height="360" src="https://www.youtube.com/embed/4lq_6JLR2H8?rel=0" frameborder="0" allowfullscreen></iframe>

In case you don't have time to view the entire video, here are some quick examples to get you up to speed. In essence to package allow you to associate files with Eloquent models.

```
$yourModel
    ->addMedia($filePath)
    ->toMediaCollection();
```

This will move the file at `$filePath` to the medialibrary and associate a new `Media` model with `$yourModel`. You can add as many files as you'd like.

To get all associated media you can do this:

```
$yourModel->getMedia();
```

This will return a `Collection` of associated `Media` models. Because I found that the first associated media is often need there's a `getFirstMedia` method that does exactly what it's name implies.

A media object you can determine the url and path to it's associated file.

```
$yourModel->getFirstMedia()->getUrl() // returns an url
$yourModel->getFirstMedia()->getPath() // returns the full path
```

If you delete models that have associated media, the medialibrary will also clean up the files. You don't need to clean up after yourself.

```
// all related files will be deleted from the filesystem
$yourModel->delete();
```

Media can be put in so called media collections. It's really nothing more the putting a name on a group of media so you can retrieve them together.

```
$yourModel
    ->addMedia($pathToImage)
    ->toMediaCollection('images');
    
$yourModel
    ->addMedia($pathToAnotherImage)
    ->toMediaCollection('images');
    
$yourModel
    ->addMedia($pathToPdf)
    ->toMediaCollection('downloads');
    
$yourModel
    ->addMedia($pathToAnotherPdf)
    ->toMediaCollection('downloads');
    
$yourModel->getMedia('images'); // only get media in the images collection

$yourModel->getMedia('downloads'); // only get media in the downloads collection
```

The medialibrary can also store things on external filesystems. Under the hood Laravel's native cloud filesystem is used. So you can store your media on all filesystem for which there is a FlySystem driver (S3, Dropbox, Google Drive, SFTP, ...)

Storing a media on a remote filesystem is laughably easy. Just use the disk name as the second parameter of `toMediaCollection`.

```
// the image will be uploaded to s3
$yourModel
    ->addMedia($pathToImage)
    ->toMediaCollection('images', 's3');
    
$yourModel->getFirstMedia('images')->getUrl(); // returns an url to the file on s3
```

The medialibrary can also generate derived images. Image you want to create a small thumbnail of an image. That's easy! On your eloquent model you should define a media conversion.

```
// in your model
 public function registerMediaConversions()
{
    $this
		    ->addMediaConversion('thumb')
        ->width(50)
        ->height(50);
 }
```

Whenever you associate media with your model a derived image will be created that will fit in a 50x50 box, aspect ratio will be respected.

```
$yourModel
    ->addMedia($pathToImage)
    ->toMediaCollection();
    
$yourModel->getFirstMedia()->getUrl() // url to original image
$yourModel->getFirstMedia()->getUrl('thumb') // url to the thumbnail version.
```

By default all image conversions are queued. You can have as many image conversions on a model as you'd like. Under the hood our [image package](https://docs.spatie.be/image) is used, so you can any of it's many supported manipulations.

The above examples are only a tip of the iceberg. If you want to know more about all available features, go read [the extensive documentation](https://docs.spatie.be/laravel-medialibrary) we've written for this package.

With this general introduction out of the way, let's focus on the new features that have been added to the shiny new v7 of the package.

## New in v7: progressive image loading and responsive images #

One of the most important additions in v7 is the support for responsive images. Websites are viewed on various devices with widely differing screen sizes and connection speeds. When serving images it's best not to use the same image for all devices. A large image might be fine on a desktop computer with a fast internet connection, but on a small mobile device with limited bandwhith, the download might take a long time.

The most common way to display a picture is by using an `img` element with a `src` attribute.

```
<img src="my-image.jpg">
```

Using this markup the browser will always display `my-image.jpg` regardless of screen size.

As described in the [HTML specification](https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset) you can also use a `srcset` attribute to indicate different versions of your image and their respective width.

```
<img src="large.jpg" srcset="large.jpg 2400w, medium.jpg 1200w, small.jpg 600w">
```

When using `srcset`, the browser will automatically figure out which image is best to use.

Responsive images are nice addition. But of course to display an image it needs to come over the wire first. On bad network condition even a small image download can take a while. When an image finally is downloaded the layout of the page is potentially rearrenged, which can be distracting.

In order to solve this problem support for progressive image loading was added to the medialibrary. You might have seen this technique before at [Medium] blogs (here's [an example](https://hackernoon.com/value-objects-like-a-pro-f1bfc1548c72). You see a blurred image first. After a while that images gets replaced by the real one.

The medialibrary can generate both the blurred placeholder and all responsive versions of the image (these go in the `srcset` attribute) for you. Simply use the `withResponsiveImages` function when adding media to the medialibrary.

```
$yourModel
   ->addMedia($yourImageFile)
   ->withResponsiveImages()
   ->toMediaCollection();
```

By default the medialibrary uses an algorithm which produces responsive images that have ±70% of the filesize of the previous variation. It will keep generating variations until the predicted file size is lower then 10 Kb or the target width is less than 20 pixels. So for an image with large dimensions the medialibrary will generate more variations than for an image with smaller dimensions.

In order to display responsive images you can simply output an instance of media in Blade view.

```
// we use a closure route here, normally you'd do this in a controller

Route::get('showing-responsive-images', function() {
    $yourModel->getFirstMedia();

    return view('showing-responsive-images', compact('media'));
});
```

The blade view could look like this:

```
{{-- Yup, there's nothing more to it --}}
{{ $media }}
```

This will output html that will look like:

```
<img
	 srcset="
	 	 /media/1/responsive-images/test___medialibrary_original_2048_1536.jpg 2048w,
	 	 /media/1/responsive-images/test___medialibrary_original_1713_1284.jpg 1713w,
	 	 /media/1/responsive-images/test___medialibrary_original_1433_1074.jpg 1433w,
	 	 /media/1/responsive-images/test___medialibrary_original_1199_899.jpg 1199w,
	 	 /media/1/responsive-images/test___medialibrary_original_1003_752.jpg 1003w, 
	 	 /media/1/responsive-images/test___medialibrary_original_839_629.jpg 839w, 
	 	 /media/1/responsive-images/test___medialibrary_original_702_526.jpg 702w,
	 	 /media/1/responsive-images/test___medialibrary_original_587_440.jpg 587w,
	 	 /media/1/responsive-images/test___medialibrary_original_491_368.jpg 491w,
	 	 /media/1/responsive-images/test___medialibrary_original_411_308.jpg 411w,
	 	 /media/1/responsive-images/test___medialibrary_original_344_258.jpg 344w,
	 	 /media/1/responsive-images/test___medialibrary_original_287_215.jpg 287w,
	 	 /media/1/responsive-images/test___medialibrary_original_240_180.jpg 240w, 
	 	 data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHg9IjAiCiB5PSIwIiB2aWV3Qm94PSIwIDAgMjA0OCAxNTM2Ij4KCTxpbWFnZSB3aWR0aD0iMjA0OCIgaGVpZ2h0PSIxNTM2IiB4bGluazpocmVmPSJkYXRhOmltYWdlL2pwZWc7YmFzZTY0LC85ai80QUFRU2taSlJnQUJBUUVBWUFCZ0FBRC8vZ0E3UTFKRlFWUlBVam9nWjJRdGFuQmxaeUIyTVM0d0lDaDFjMmx1WnlCSlNrY2dTbEJGUnlCMk9UQXBMQ0J4ZFdGc2FYUjVJRDBnT1RBSy85c0FRd0FEQWdJREFnSURBd01EQkFNREJBVUlCUVVFQkFVS0J3Y0dDQXdLREF3TENnc0xEUTRTRUEwT0VRNExDeEFXRUJFVEZCVVZGUXdQRnhnV0ZCZ1NGQlVVLzlzQVF3RURCQVFGQkFVSkJRVUpGQTBMRFJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVUvOEFBRVFnQUdBQWdBd0VpQUFJUkFRTVJBZi9FQUI4QUFBRUZBUUVCQVFFQkFBQUFBQUFBQUFBQkFnTUVCUVlIQ0FrS0MvL0VBTFVRQUFJQkF3TUNCQU1GQlFRRUFBQUJmUUVDQXdBRUVRVVNJVEZCQmhOUllRY2ljUlF5Z1pHaENDTkNzY0VWVXRId0pETmljb0lKQ2hZWEdCa2FKU1luS0NrcU5EVTJOemc1T2tORVJVWkhTRWxLVTFSVlZsZFlXVnBqWkdWbVoyaHBhbk4wZFhaM2VIbDZnNFNGaG9lSWlZcVNrNVNWbHBlWW1acWlvNlNscHFlb3FhcXlzN1MxdHJlNHVickN3OFRGeHNmSXljclMwOVRWMXRmWTJkcmg0dVBrNWVibjZPbnE4Zkx6OVBYMjkvajUrdi9FQUI4QkFBTUJBUUVCQVFFQkFRRUFBQUFBQUFBQkFnTUVCUVlIQ0FrS0MvL0VBTFVSQUFJQkFnUUVBd1FIQlFRRUFBRUNkd0FCQWdNUkJBVWhNUVlTUVZFSFlYRVRJaktCQ0JSQ2thR3h3UWtqTTFMd0ZXSnkwUW9XSkRUaEpmRVhHQmthSmljb0tTbzFOamM0T1RwRFJFVkdSMGhKU2xOVVZWWlhXRmxhWTJSbFptZG9hV3B6ZEhWMmQzaDVlb0tEaElXR2g0aUppcEtUbEpXV2w1aVptcUtqcEtXbXA2aXBxckt6dExXMnQ3aTV1c0xEeE1YR3g4akp5dExUMU5YVzE5aloydUxqNU9YbTUranA2dkx6OVBYMjkvajUrdi9hQUF3REFRQUNFUU1SQUQ4QStFanBkeG9Gd2wwWTJDNTY0cjE3d0pIZjZyRkhkTEorNlBHMnZxU1g5bXZSOWIrSG9NMEtpZmFTSHgzcjU2c2RKdlBoanJjMWpOSHZ0MWJFZWF2TDhTc1p6Y3NXckcyT3djcVVVMXJjeVBHVnZwbW1TRjdvK1ZKakl6NjE0OTR0dVQ0amxXTzNPNUY0elhzWHhDOEY2aDR6a2p1bGpaVWJvb0ZlT2VNdkMrcStBcGxTZU5vMWZrWkZkdGFqWnR0bkRCT0s1V2o3bDFqOXBJYWI0VVd6aSs4QjFyNXQrSWZ4RHZOZnUwdTFmNXQzRkZGZUxsODVVYWxvUGRuME9JZDZXcFkwZjR3NmhvQzIwdDZxeVc2WU9DSzV2NHkvRVdINHBQRExGQ3NRakdPQlJSWDJGU2pDcXB6bHVqNTlTYmR6LzlrPSI+Cgk8L2ltYWdlPgo8L3N2Zz4= 32w" 
	 onload="this.onload=null;this.sizes=Math.ceil(this.getBoundingClientRect().width/window.innerWidth*100)+'vw';" 
	 sizes="1px" 
	 src="/media/1/test.jpg"
	 width="2048">
```

You can see all the image variations in the `srcset` attribute. The last item in that attribute is an inlined version of the blurred tiny version of the image. It's inlined so that it can be displayed immediately, no extra network request needed.

Notice that we set the `sizes` attribute to `1px`. This will make the browser pick the blurred version by default. The little bit of JavaScript in `onload` is responsible for replacing the blurred version with the real version once it has been downloaded. A cool side effect of this is that if you make your browser window larger, it will try to download an image for the new size of the window. It wil show that bigger image as soon as it has been downloaded.

If you want to see an example of this behaviour, point your browser to [this page](https://docs.spatie.be/laravel-medialibrary/demo/responsive-images) in our docs.

The medialibrary makes it incredibly easy to get started with responsive images + progressive images loading. The only things you have to do is to use `withResponsiveImages()` when adding media and then ouput a media object in a Blade view. I hope this feature really can make a difference. I can't wait to see it being used in the wild.

## New in v7: multi file downloads #

In the medialibrary 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](https://github.com/spatie/laravel-medialibrary/blob/ac8b8df1d2266260a20dc181b528050e09b1c9b4/src/Media.php#L236-L242) [Laravel's Responsable interface](https://laravel-news.com/laravel-5-5-responsable) 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?

Medialibrary v7 includes a new `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](https://github.com/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%.

## New in v7: supercharged media collections #

Media collections already exist in the current version of the medialibrary. They allow you to put different types of files in their own collection.

Let's associate some media:

```
$yourModel
   ->addMedia($pathToImage)
   ->toMediaCollection('images');

$yourModel
   ->addMedia($pathToAnotherImage)
   ->toMediaCollection('images');
   
$newsItem
   ->addMedia($pathToPdfFile)
   ->toMediaCollection('downloads');
   
$newsItem
  ->addMedia($pathToAnExcelFile)
  ->toMediaCollection('downloads');
```

All media in a specific collection can be retrieved like this:

```
// will return media instances for all files in the images collection
$yourModel->getMedia('images');

// will return media instances for all files in the downloads collection
$yourModel->getMedia('downloads');
```

In v7 a media collection can be more than just a name to group files. By defining a media collection in your model you can add certain behaviours to collections.

To get started with media collections add a function called `registerMediaCollections` to [your prepared model](https://docs.spatie.be/laravel-medialibrary/v7/basic-usage/preparing-your-model). Inside that function you can use `addMediaCollection` to start a media collection.

```
// in your model

public function registerMediaCollections()
{
    $this->addMediaCollection('my-collection')
        //add options
        ...

    // you can define as much collections as needed
    $this->addMediaCollection('my-other-collection')
        //add options
        ...
}
```

You can pass a callback to `acceptsFile` that will check if a file is allowed into the collection. In this example we only accept `jpeg` files:

```
use Spatie\MediaLibrary\File;
...
public function registerMediaCollections()
{
    $this
        ->addMediaCollection('only-jpegs-please')
        ->acceptsFile(function (File $file) {
            return $file->mimeType === 'image/jpeg';
        });
}
```

This will succeed:

```
$yourModel
  ->addMedia('beautiful.jpg')
  ->toMediaCollection('only-jpegs-please');
```

This will throw a `Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\FileUnacceptableForCollection` exception:

```
$yourModel
   ->addMedia('ugly.ppt')
   ->toMediaCollection('only-jpegs-please');
```

You can use media collections to ensure that files added to a collection are automatically added to a certain disk.

```
// in your model

public function registerMediaCollections()
{
    $this
       ->addMediaCollection('big-files')
       ->useDisk('s3');
}
```

When adding a file to `my-collection` it will be stored on the `s3` disk.

```
$yourModel->addMedia($pathToFile)->toMediaCollection('big-files');
```

You can still specify the disk name manually when adding media. In this example the file will be stored on `alternative-disk` instead of `s3`.

```
$yourModel
   ->addMedia($pathToFile)
   ->toMediaCollection('big-files', 'alternative-disk');
```

If you want a collection to hold only one file you can use `singleFile` on the collection. A good use case for this would be an avatar collection on a `User` model. In most cases you'd want to have a user to only have one `avatar`.

```
// in your model

public function registerMediaCollections()
{
    $this
        ->addMediaCollection('avatar')
        ->singleFile();
}
```

The first time you add a file to the collection it will be stored as usual.

```
$yourModel
   ->add($pathToImage)
   ->toMediaCollection('avatar');
   
$yourModel->getMedia('avatar')->count(); // returns 1

$yourModel->getFirstUrl('avatar'); // will return an url to the `$pathToImage` file
```

When adding another file to a single file collection the first one will be deleted.

```
// this will remove other files in the collection
$yourModel
   ->add($anotherPathToImage)
   ->toMediaCollection('avatar');
   
$yourModel->getMedia('avatar')->count(); // returns 1

$yourModel->getFirstUrl('avatar'); // will return an url to the `$anotherPathToImage` file
```

In the general introduction I've mentioned that you can use `registerMediaConversions` to define media conversions. However, images conversions can also be registered inside media collections.

```
public function registerMediaCollections()
{
    $this
        ->addMediaCollection('my-collection')
        ->registerMediaConversions(function (Media $media) {
            $this
                ->addMediaConversion('thumb')
                ->width(100)
                ->height(100);
        });
}
```

When adding an image to `my-collection` a thumbnail that fits inside 100x100 will be created.

```
$yourModel->add($pathToImage)->toMediaCollection('my-collection');

$yourModel->getFirstMediaUrl('thumb') // returns an url to a 100x100 version of the added image.
```

## In closing #

In addition to the features described above we did some refactoring of the code, improved the testsuite and polished some functionality:

- files will now be lowercased when adding them to the medialibrary.
- for seo purposes converted images will now included the filename of the original file in their filename.
- `move` and `copy` methods have been added to `Media` so you can easily move media between models.

There are a lot of features not touched upon in this blogpost, such as [image generators](https://docs.spatie.be/laravel-medialibrary/v7/converting-other-file-types/using-image-generators), using a [custom directory structures](https://docs.spatie.be/laravel-medialibrary/v7/advanced-usage/using-a-custom-directory-structure), [optimizing images](https://docs.spatie.be/laravel-medialibrary/v7/converting-images/optimizing-converted-images), [using your own model](https://docs.spatie.be/laravel-medialibrary/v7/advanced-usage/using-your-own-model), ... Head over to [the documentation of the package](https://docs.spatie.be/laravel-medialibrary) to learn it all.

In the next couple of months we're planning to add some Vue components to the package. These components will make it easy to upload stuff into the medialibrary. We'll also add components that are able to administer uploaded media in a collection.

Originally these components were going to be part of v7. We need more time to finish them. Probably we'll be able to add the components without having to introduce breaking changes.

I hope you liked this rundown of the new features in v7. Personally I can't wait to use it in our new projects.

Medialibrary is not the only package our team at Spatie has made. Be sure to check out [all other packages](https://spatie.be/en/opensource) listed on our company site. Don't forget to [send us a postcard](https://github.com/spatie/laravel-medialibrary#postcardware) if any of our stuff makes in into your production environment.