Thumbnailing with LiipImagineBundle

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Go back to the homepage. We're rendering these images with a width and height of 100. But the image behind this is way bigger! That's wasteful: we don't want the user to wait to download these gigantic images, just to see the tiny thumbnail.

Hello LiipImagineBundle

Google for LiipImagineBundle and find its GitHub page. They have a bunch of docs right here... but most of the information actually lives over on Symfony.com. Click "Download the Bundle" to get there... and then I'll go back to the homepage - lots of good stuff here.

Start back on the Installation page. Copy the composer require line, find your terminal, paste and... go go go!

composer require liip/imagine-bundle

While we're waiting, head back over to the docs. Thanks to Flex, we don't need to enable the bundle or register the routes - that's automatic. Go back to the homepage of the docs... and click the "Filter Sets" link.

This bundle is pretty sweet. You start by creating something called a "filter set" and giving it a name - like my_thumb or whatever you want. Next, you tell the bundle which filters, or transformations, to apply when you use the my_thumb filter set. And there are a ton of them: you can change the size with the thumbnail filter, add a background, add border color, replace the image entirely with a cat gif - pretty much anything you can dream of. We'll just use the thumbnail transformation, but seriously - check out the full list.

Configuring the Filter Set

Let's go check on the install. Excellent! It's done. And the message is right on: it says we need to get to work in the new config file: liip_imagine.yaml. Go open that: config/packages/liip_imagine.yaml. Uncomment the root key to activate the bundle, leave the driver alone - it defaults to gd - and uncomment filter_sets.

liip_imagine:
# # valid drivers options include "gd" or "gmagick" or "imagick"
# driver: "gd"
#
# # define your filter sets under this option
filter_sets:
... lines 7 - 42

Let's create our first filter set called squared_thumbnail_small. We'll use this on the homepage to reduce the images down to 100 by 100. To do that, uncomment the filters key and I'll copy the thumbnail example from below, move it up here, and uncomment it.

liip_imagine:
... lines 2 - 5
filter_sets:
... lines 7 - 9
squared_thumbnail_small:
filters:
... lines 12 - 42

Set the size to 200 by 200 so it looks good on Retina displays. The mode: outbound is how the thumbnail is applied - you can also use inbound.

liip_imagine:
... lines 2 - 5
filter_sets:
... lines 7 - 9
squared_thumbnail_small:
filters:
thumbnail:
size: [200, 200]
mode: outbound
allow_upscale: true
... lines 17 - 42

And... I think we're ready to go! Copy the squared_thumbnail_small name and go into homepage.html.twig. To use this, it's so nice: |imagine_filter() and then the name.

... lines 1 - 2
{% block body %}
... lines 4 - 20
{% for article in articles %}
<div class="article-container my-1">
<a href="{{ path('article_show', {slug: article.slug}) }}">
<img class="article-img" src="{{ uploaded_asset(article.imagePath)|imagine_filter('squared_thumbnail_small') }}">
... lines 25 - 37
</a>
</div>
{% endfor %}
... lines 41 - 63
{% endblock %}

The Thumbnailing Process

Let's go try it! Watch the image src closely. Refresh! It includes the https://127.0.0.1 part, but that's not important. The path - /media/cache/resolve/squared_thumbnail_small/... blah, blah blah - looks like a path to a physical file, but it's not! This is actually a Symfony route and it's handled by a Symfony controller!

Check it out: at your terminal, run:

php bin/console debug:router

There it is! The first time we refresh, LiipImagineBundle generates this URL. When our browser tries to download the image, it's handled by a controller from the bundle. That controller opens the original image, applies all the filters - just a thumbnail in our case - and returns the transformed image. That's a slow operation: our browser has to wait for all of that to finish.

But, watch what happens when we refresh. Did you see it? The path changed! It was /media/cache/resolve - but the resolve part is now gone! This time, the image is not handled by a Symfony route. Look at your public/ directory: there is now a media/ directory with cache/squared_thumbnail_small/uploads/article_image/astronaut-...jpeg.

The full process looks like this. The first time we refreshed, LiipImagineBundle noticed that no thumbnail file existed yet. So, it created the URL that pointed to the Symfony route & controller. The page finished rendering, and our browser make a second request to that URL to load the image. That request was handled by the controller from the bundle which thumbnailed the image, saved it to the filesystem, and returned it to the user. That's slow.

But when we reloaded the page the second time, LiipImagineBundle noticed that the filename already existed and generated a URL directly to that real file. The request for that image was super fast.

Oh, also check out the .gitignore file. Thanks to the Flex recipe, we're already ignoring the public/media directory: we do not want to commit this stuff: it'll just regenerate if it's missing.

So, yea - it all kinda works perfectly!

Next, let's add another filter set for the show page and add an image preview to the article form.

Leave a comment!

This tutorial is built on Symfony 4 but works great in Symfony 5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.87.10
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.3
        "symfony/console": "^4.0", // v4.2.3
        "symfony/flex": "^1.9", // v1.9.10
        "symfony/form": "^4.0", // v4.2.3
        "symfony/framework-bundle": "^4.0", // v4.2.3
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.3
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.3
        "symfony/validator": "^4.0", // v4.2.3
        "symfony/web-server-bundle": "^4.0", // v4.2.3
        "symfony/yaml": "^4.0", // v4.2.3
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.3
        "symfony/dotenv": "^4.0", // v4.2.3
        "symfony/maker-bundle": "^1.0", // v1.11.3
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.3
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.3
    }
}