Accéder au contenu principal

14min.

JoliMediaBundle, a new media bundle for your Symfony projects

Cet article est aussi disponible en 🇫🇷 Français : JoliMediaBundle, un nouveau bundle de médias pour vos projets Symfony.

This is a short story that begins in early 2025 when, while proofreading an article we were about to publish on the JoliCode blog, I noticed an image with rather questionable graphic quality.

Comparison of a source image and its alternative version, compressed by the old media processor on the JoliCode website

What a shame to illustrate this article with a diagram featuring washed-out colors and lines that aren’t exactly sharp”, I said to myself at that moment. I headed to the site’s administration interface to replace this image with a better quality version. And there, surprise: the source image, used to illustrate the article, was perfectly sharp and vibrant!

This was the beginning of a long quest that, one thing leading to another, led to the writing of a new Symfony media management bundle: JoliMediaBundle.

Section intitulée what-is-it-exactly-a-media-managerWhat is it exactly, a media manager?

Throughout the many Symfony projects we have conducted over the last 15 years, we have frequently faced the need to set up a media library, a file explorer, upload functionalities, or similar functionalities.

This type of need appears regularly in the projects we lead, whether they are showcase websites, eCommerce platforms, or business applications. Indeed, multimedia content management is an essential component of many modern Web applications: if my site presents articles, there is a good chance I will want to illustrate them with images. If my site is a shop selling products, visuals will be needed to present them, etc.

More specifically, in our case, the notion of media management covers different aspects: uploading, storage, organization, transformation (resizing, cropping), optimization (compression, format change), delivery, selection and association with business entities, display (in Web pages), etc.

Let’s review these different aspects:

  • uploading media consists of offering users a pleasant experience for sending files from their computer or mobile device to the server. This can include features such as drag-and-drop, multiple file selection, upload progress tracking, and file type and size validation;
  • storage of media involves deciding where and how files will be kept. This can be on the server’s file system or via an online storage service (like AWS S3, Google Cloud Storage, etc.). It is important that the chosen approach is scalable, secure, and adapted to the project’s needs. For example, if the project needs to manage a very large volume of media, the SSD of a VPS might not be the appropriate solution. Conversely, for a small showcase site, local storage may be sufficient, and it is likely unnecessary to complicate the project by integrating an online storage service;
  • organization of media is crucial to allow users to easily find the files they need. This can include creating folders and subfolders, setting up a search system, or even adding metadata to media. The goal is to make the media library intuitive and efficient, and to prevent users from eventually storing all their files in a single root folder out of frustration (or losing them in an overly complex tree structure and ending up uploading the same file 10 times);
  • transformation of media is often necessary to adapt files to the specific needs of the project. For example, it is certainly necessary to automatically resize images so that they display correctly on different devices (desktop, tablet, mobile). If the administrator provides very high-resolution photos for their catalog products, it is likely that they will need to be cropped or resized before being displayed on the site;
  • optimization of media is also important to ensure fast loading times and a good user experience. This can include compressing media to reduce their size (for example, by using slightly more aggressive graphic settings), and converting them into more efficient formats (like WebP for images);
  • delivery of media consists of making files accessible to end users. Without necessarily addressing the notion of a CDN, the role of a media management software brick is to provide readable URLs (i.e., understandable, including the file name, etc.) and stable ones (i.e., that do not change with every deployment) to access media;
  • selection of media is a key feature to allow users to easily associate files with the content they create. For example, when an administrator writes a blog post, they must be able to select an illustration image from the media library, rather than having to upload a new file every time;
  • display of media in Web pages is the final step of the process. It involves ensuring that files are correctly integrated into HTML pages, using the right tags, appropriate attributes (like srcset attributes for responsive images), and ensuring that media display correctly on all devices.

That’s a lot of things, isn’t it? 😅 In reality, creating “well-made” Web projects systematically requires taking all these little details into account, along with thousands other things! Capitalizing on the knowledge we have accumulated regarding media management therefore responds to an important challenge of quality and productivity: if we had a standard media management solution available, we could use it for all our projects.

Section intitulée and-why-don-t-you-just-use-a-cmsAnd why don’t you just use a CMS?

Good question! Indeed, CMS (Content Management Systems) such as Drupal, WordPress, or Joomla generally integrate content and media management functionalities. So, why not simply use a CMS to manage media in our Symfony projects? Or, another approach, why not rule out the media management issue by systematically using a third-party service dedicated to this problem (for example, a specialized Media Management SaaS)?

There are several reasons for this.

First, a CMS is designed to manage the entire content lifecycle, not just media management. This means that using a CMS to manage media in a Symfony project can introduce unnecessary complexity, and potentially limitations in terms of customization and integration with the specific business functionalities of the project. Concretely, integrating a market CMS with a custom application is feasible, but it often requires considerable effort to ensure seamless integration between the two systems – what one might call a “Frankensteinian setup.”

The choice between an integrated solution (a CMS) and a custom solution (like JoliMediaBundle) is not a brand new topic: it is a debate that has existed for a long time in the world of Web development. However, in the specific context of the Symfony projects we lead, we have found that the flexibility and customization offered by a custom solution are often better suited to our clients’ needs.

Often, the needs directly related to media management are relatively simple and can be implemented quickly and efficiently within the Symfony application itself, without having to resort to a complete CMS. Symfony has long offered highly productive bundles for rapidly creating administration interfaces – such as EasyAdmin or SonataAdmin – which are real productivity accelerators for quickly setting up custom contribution interfaces directly integrated into the application. Under these conditions, adding a CMS can turn out to be unnecessary overhead, complicating the development and maintenance of the application, whereas a few entities do the job perfectly.

On the other hand, one might think that media management is necessarily a complicated subject, and that it is better to use an online service rather than implementing anything on the subject (read: a SaaS specialized in media management). There are several SaaS solutions on the market to manage media and associated aspects (upload widget / storage / resizing and optimization / CDN / etc.). Again, this is an approach that can be relevant in certain contexts, but which also presents significant drawbacks:

  • first, recurring costs (often far from trivial, especially if the volume of media is large). These services often charge for bandwidth, so if your site generates significant traffic, the bill can rise very quickly;
  • a dependency on a third-party service (which can create privacy or compliance problems, depending on the nature of the managed media);
  • limited flexibility (the features offered by the service may not match the project’s needs exactly, and it may be difficult or impossible to adapt them);
  • increased architecture complexity (integration between the Symfony application and the third-party service must be observed, as it can introduce additional points of failure);
  • a more complex situation for development stacks, continuous integration, and long-term project maintenance (local testing becomes more complicated, for example);
  • and in some cases, it just doesn’t correspond to the business usage of the project. If my application needs to allow organizing PDF meeting reports, does it make sense to use a SaaS specialized in media management, which will be primarily optimized for images and videos?

Ultimately, choosing to develop custom content management features (this is the category media management falls into) is often a simple, economical approach adapted to the specific needs of projects. Good reasons can lead to other choices, and there is no universal solution.

Section intitulée let-s-get-back-to-business-why-a-new-media-bundle-for-symfonyLet’s get back to business: why a new media bundle for Symfony?

Back to our initial story: after noting that the image illustrating our blog post was of poor quality, despite the presence of a correct source version in the administration interface, I started investigating this extremely serious problem! 😋

For a few years now, we have been embedding images in our blog articles using <picture> tags with generally two sources: a version in the original format (JPEG or PNG), and a WebP version. The goal is to take advantage of the benefits of the WebP format (better compression, preserved visual quality) while ensuring compatibility with browsers that do not yet support this format (incidentally, we frequently debate internally: for or against simply abandoning historical image formats in favor of more modern formats like WebP?).

This conversion was handled by LiipImagineBundle, a very popular Symfony bundle for image management and transformation, which we have happily used for years in many projects. It is a bundle developed by our colleagues at Liip, and which wraps the Imagine library, a PHP image manipulation library that abstracts graphic processing over several PHP extensions (GD, Gmagick, Imagick, etc.).

Digging little by little, I first tried to improve our LiipImagineBundle configuration by increasing the quality levels, but that didn’t really have the desired effects: the image was still just as ugly, and its weight had increased significantly. By performing additional tests, it appeared that there was unfortunately no easy fix to apply: the choice of abstraction offered by Imagine, although very practical, imposes significant limitations in terms of the quality of the images produced.

It is actually the global process that is at fault in the specific case of our use of LiipImagineBundle: to serve an optimized WebP image, the source image is converted into an Imagick resource, which undergoes as many successive transformation steps as requested in the bundle configuration (resizing, cropping, etc.), and is finally exported in WebP format. And then, this WebP image is optimized by the WebP post-processor to try to reduce its size.

Each step of this process causes a loss of quality, and the accumulation of these losses leads to a disappointing final result. In reality, even from the opening of the source image and its conversion into an Imagick object, the majority of graphic quality is lost… And there isn’t really a cure for that, except defining a quality level of 100% in the Imagine configuration which, obviously, generates huge files.

By analyzing all the treatments, I even found it a shame that, to generate a WebP image from a source PNG, the processing goes through multiple intermediate conversions (PNG -> Imagick Resource -> Resized Resource -> WebP -> WebP optimized by cwebp):

Resizing and optimization with Imagine, in several steps

However, the cwebp binary is capable of directly converting a PNG to WebP, resizing it, and cropping it in a single step, with fine control over optimization parameters. This was the starting point for the development of JoliMediaBundle, starting from the idea that it would undoubtedly be possible to do better by freeing ourselves from the limitations imposed by Imagine:

Resizing and optimization in one step with cwebp

Section intitulée okay-and-what-are-the-features-of-this-jolimediabundleOkay, and what are the features of this JoliMediaBundle?

JoliMediaBundle is therefore an open source Symfony bundle, developed by JoliCode, which aims to provide a complete and performant solution for media management in Symfony applications. Here is an overview of the main features offered by JoliMediaBundle:

  • Integration with EasyAdmin and Sonata Admin: JoliMediaBundle integrates perfectly with popular administration bundles, thus facilitating media management directly from the administration interface. The media library is graphically well integrated into the visual universe of these bundles, and offers different display modes (list, grid, etc.), as well as multiple upload and intuitive selection features;
  • Flexible storage: the PHP ecosystem already has excellent file storage abstraction solutions, and we weren’t going to reinvent the wheel! JoliMediaBundle relies on the Flysystem component to offer maximum flexibility in terms of storage;
  • Transformation and optimization: the bundle offers a concept of “variation”, which allows defining rules for media transformation and optimization. For example, it is possible to create a “thumbnail” variation that resizes images to a specific size. Different types of transformations are supported (crop, resize, expand, heighten, etc.);
  • Efficient delivery: JoliMediaBundle generates stable and readable URLs for accessing media, thus facilitating their delivery to end users;
  • Optimized display: the bundle offers Twig components to easily integrate media into Web pages, taking into account best practices (srcset attributes, lazy loading, etc.).

Let’s review some of these features!

Section intitulée media-transformation-and-optimizationMedia transformation and optimization

JoliMediaBundle allows defining “variations” of media, which are sets of transformation and optimization rules. For example, one can define a variation named “my_thumbnail” to resize images to a specific size. Here is an example of YAML configuration to define such a variation:

variations:
    my_thumbnail:
        transformers:
            thumbnail:
                width: 200
                height: 200

The reference documentation for transformations provides various examples of possible configurations:

Examples of transformations supported by JoliMediaBundle

Section intitulée integration-with-easyadmin-and-sonata-adminIntegration with EasyAdmin and Sonata Admin

The bundle proposes, through two complementary bundles (JoliMediaEasyAdminBundle and JoliMediaSonataAdminBundle), transparent integration with the two most popular administration bundles in the Symfony ecosystem. These two bundles are directly provided with JoliMediaBundle, and you just need to activate them in your Symfony application to benefit from them.

The ergonomics of the media library have been carefully thought out to offer a pleasant and efficient user experience. The two bridges have similar quality levels and offer comparable features. Simply put, the visual appearance is adapted to each administration bundle for harmonious integration into the existing interface. Here are some screenshots illustrating the integration of JoliMediaBundle with EasyAdmin and Sonata Admin:

Integration of JoliMediaBundle with EasyAdmin and Sonata Admin

Here are various key features of this integration:

  • File explorer: an intuitive file explorer allows easy navigation through the media library tree structure, in the form of a list or grid view;
  • Multiple upload: the multiple upload feature allows adding several files in a single operation, with upload progress tracking;
  • Intuitive selection: when creating or modifying business entities, it is possible to easily select or add media from the media library in a modal window;
  • Lifecycle management: features for deleting, renaming, and moving media are available directly from the administration interface.

Section intitulée displaying-images-in-web-pages-with-code-lt-img-gt-code-and-code-lt-picture-gt-code-tagsDisplaying images in Web pages with <img /> and <picture /> tags

Inserting images into Web pages is a common task, but one that can quickly become complex if one wishes to respect best practices regarding performance and accessibility. It still happens too commonly that content negotiation is not taken into account, that srcset and sizes attributes are not used correctly, or that lazy loading is not implemented.

JoliMediaBundle therefore offers dedicated Twig components to facilitate this task. For example, to insert an image with a specific variation, simply use the joli:Img component:

<twig:joli:Img
    path="example-image.png"
    variation="variation_name"
    alt="Alternative text"
/>

…which will produce the following HTML markup (in the example below, the width and height attributes are automatically added to limit Cumulative Layout Shift (CLS)):

<img
    src="/path/to/cache/variation-name/example-image.png"
    alt="Alternative text"
    width="200"
    height="200"
    loading="lazy"
    decoding="async"
/>

The joli:Img and joli:Picture Twig components automatically handle best practices, such as lazy loading, adding width and height attributes, and generating <source> tags for the WebP format in the case of the joli:Picture component. These default behaviors can obviously be overridden according to the specific needs of your projects.

Section intitulée debugging-and-analysisDebugging and analysis

Understanding the transformations applied to media can sometimes be complex, especially when several variations come into play on the same page. To help developers better understand what is happening “under the hood, ” JoliMediaBundle offers an integrated debugging tool, accessible via the Symfony Web Debug Toolbar and the Profiler. This tool provides detailed information on the transformations applied to media, available variations, etc.

A capture of the JoliMediaBundle profiler panel

Section intitulée use-in-doctrine-entitiesUse in Doctrine entities

JoliMediaBundle provides a custom Doctrine field type, MediaType, which facilitates the association of media with business entities. Here is an example of usage in a Doctrine entity:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use JoliCode\MediaBundle\Doctrine\Types as MediaTypes;
use JoliCode\MediaBundle\Model\Media;

#[ORM\Entity]
class Article
{
    // ...

    #[ORM\Column(type: MediaTypes::MEDIA, nullable: true)]
    public ?Media $image = null;
}

When retrieving the entity, the image field will contain an instance of JoliCode\MediaBundle\Model\Media, which you can use with Twig components to display the image in your templates.

It is also possible to define validation criteria for Media type fields, to ensure that associated media respect certain constraints (MIME type, maximum size, etc.), and to define the behavior to adopt when attempting to delete a media that is referenced by a Doctrine entity (forbid deletion, set the field to null, etc.):

use JoliCode\MediaBundle\DeleteBehavior\Attribute\MediaDeleteBehavior;
use JoliCode\MediaBundle\DeleteBehavior\Strategy;
use JoliCode\MediaBundle\Validator\Media as MediaConstraint;

#[ORM\Entity]
class Article
{
    // ...

    #[ORM\Column(type: MediaTypes::MEDIA, nullable: false)]
    #[MediaConstraint(
        allowedPaths: ['illustration', 'avatar'],
        allowedMimeTypes: ['image/jpeg', 'image/png'],
    )]
    #[MediaDeleteBehavior(strategy: Strategy::RESTRICT)]
    public ?Media $requiredImage;
}

Section intitulée an-internal-api-to-manipulate-media-efficientlyAn internal API to manipulate media efficiently

The bundle’s internal API uses a few key concepts:

  • retrieving a media or one of its variations is done through the resolution service, the Resolver;
  • executing a media conversion is performed by the Converter;
  • one typically does not directly manipulate a file or a path, but rather a business model, the Media.
use JoliCode\MediaBundle\Conversion\Converter;
use JoliCode\MediaBundle\Library\LibraryContainer;
use JoliCode\MediaBundle\Model\Media;
use JoliCode\MediaBundle\Resolver\Resolver;

$imagePath = 'some/path/example-image.png';

// resolve the media. If this is successful, $media is an instance of JoliCode\MediaBundle\Model\Media
$media = $this->resolver->resolve($imagePath);

if (!$media instanceof Media || !\in_array($media->getMimeType(), ['image/jpeg', 'image/png', 'image/gif', 'image/webp'])) {
    throw new RuntimeException('The media could not be resolved in any of the configured storages, or it does not have a supported mime-type.', [
        'url' => $imageFilename,
    ]);
}

// generate the variation file
$this->resolver->convert($media, variationName: 'small_thumbnail');

Alternatively, you could want to retrieve the variation first, and then convert it:

use JoliCode\MediaBundle\Conversion\Converter;
use JoliCode\MediaBundle\Library\LibraryContainer;
use JoliCode\MediaBundle\Model\Media;
use JoliCode\MediaBundle\Resolver\Resolver;

$imagePath = 'some/path/example-image.png';

// resolve a specific variation of the media
$variation = $this->resolver->resolve($imagePath, variation: 'small_thumbnail');

// generate the variation file, if it does not exist yet
$this->converter->convertMediaVariation($variation, false);

// output the URL of the converted media
echo $variation->getUrl();

The bundle offers a Symfony controller to generate variations on the fly, if they do not exist yet. This allows avoiding having to pre-generate all variations for all media, which can be quite constraining and time-consuming if your media library contains many files.

Section intitulée what-this-bundle-does-not-provideWhat this bundle does not provide

The bundle is not intended to provide a complete digital asset management solution (aka a DAM), but rather to be a flexible and easy-to-use, scalable media management library that can easily be integrated into your Symfony applications and extended according to your needs. If you need a full DAM solution, you should consider other tools specifically designed for that purpose, or develop your own solution based on the bundle.

Similarly, some features are not present in this bundle (and are not intended to be). For example, advanced metadata management in the form of Doctrine entities, or AI features (face-centered cropping, image content recognition for automatic alt attribute generation, etc.) are not included in JoliMediaBundle, but could be added via extensions or integrations with other services.

Section intitulée i-m-convinced-how-do-i-test-itI’m convinced, how do I test it?

Here is a very relevant question 💛! JoliMediaBundle is an open source project, available on GitHub at https://github.com/JoliCode/MediaBundle. You can easily install it in your Symfony project by following the instructions provided in the project documentation, available at https://mediabundle.jolicode.com/.

The bundle repository also contains a demo project with EasyAdmin and Sonata Admin bridges configured.

We would be delighted to have your feedback, your suggestions for improvement, and your contributions! Do not hesitate to open issues or pull requests on the project’s GitHub repository!

This bundle is still young, but it is already used in production in several projects, which attests to its efficiency. We also have many ideas for improvements and developments, so stay tuned for future updates!

Commentaires et discussions

Nos formations sur ce sujet

Notre expertise est aussi disponible sous forme de formations professionnelles !

Voir toutes nos formations

Ces clients ont profité de notre expertise