9 min de lecture

JoliMediaBundle : Enfin un gestionnaire de médias moderne et efficace pour Symfony

Gérez facilement les médias (images) dans votre application Symfony avec JoliMediaBundle. Intégrez une médiathèque complète et une interface d'administration professionnelle en quelques minutes.

Gérez facilement les médias (images) dans votre application Symfony avec JoliMediaBundle. Intégrez une médiathèque complète et une interface d'administration professionnelle en quelques minutes.
Mode de lecture :

📖 Introduction

Gérer des médias dans Symfony, c’est souvent réinventer la roue : upload de fichiers, redimensionnement d’images, stockage, optimisation… JoliMediaBundle développé par JoliCode met fin à cette répétition en offrant une solution clé en main, testée et maintenue.

Dans ce tutoriel, nous allons créer un système d’articles avec gestion d’images en intégrant JoliMediaBundle avec EasyAdmin. À la fin, vous aurez une médiathèque complète et une interface d’administration professionnelle.

🎯 Ce que nous allons construire

Un back-office complet avec :

  • ✅ Une entité Article avec titre, contenu et image
  • ✅ Une interface EasyAdmin pour gérer les articles
  • ✅ Une médiathèque pour uploader et organiser vos images
  • ✅ Des miniatures générées automatiquement
  • ✅ Un stockage dans le filesystem (local)

🚀 Création du Projet

1. Initialisation du Projet Symfony

Si vous partez de zéro, créez un nouveau projet Symfony :

symfony new JoliBundleTest --webapp
cd JoliBundleTest
symfony serve -d
docker compose up -d

On utilise le compose.yml de base pour démarrer une instance de PostgreSQL.

📦 Installation des Bundles

1. Installation de JoliMediaBundle

composer require jolicode/media-bundle

Ce bundle installe automatiquement ses dépendances, notamment :

  • Flysystem pour la gestion du stockage
  • Symfony UX Twig Component pour les composants visuels

2. Installation d’EasyAdmin

composer require easycorp/easyadmin-bundle

3. Installation d’Imagine

JoliMediaBundle nécessite la bibliothèque Imagine pour la manipulation d’images :

composer require imagine/imagine

Configuration des Bundles

1. Enregistrement des Bundles

Vérifiez que les bundles sont bien enregistrés dans config/bundles.php :

<?php

return [
    // ... autres bundles
    Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
    EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
    JoliCode\MediaBundle\JoliMediaBundle::class => ['all' => true],
    JoliCode\MediaBundle\Bridge\EasyAdmin\JoliMediaEasyAdminBundle::class => ['all' => true],
];

Explication :

  • JoliMediaBundle : Le bundle principal
  • JoliMediaEasyAdminBundle : L’intégration avec EasyAdmin

2. Configuration de Flysystem

Ajoutez la configuration Flysystem dans config/services.yaml :

services:
    # ... configuration par défaut

    # Configuration Flysystem pour JoliMediaBundle
    filesystem.original.adapter:
        class: League\Flysystem\Local\LocalFilesystemAdapter
        arguments:
            $location: '%kernel.project_dir%/public/media/original'

    filesystem.original.storage:
        class: League\Flysystem\Filesystem
        arguments:
            $adapter: '@filesystem.original.adapter'

    filesystem.cache.adapter:
        class: League\Flysystem\Local\LocalFilesystemAdapter
        arguments:
            $location: '%kernel.project_dir%/public/media/cache'

    filesystem.cache.storage:
        class: League\Flysystem\Filesystem
        arguments:
            $adapter: '@filesystem.cache.adapter'

Explication :

  • filesystem.original.* : Services pour stocker les fichiers originaux
  • filesystem.cache.* : Services pour stocker les variations (thumbnails, etc.)
  • Nous utilisons LocalFilesystemAdapter pour un stockage sur le système de fichiers
  • En production, vous pourriez utiliser S3, Azure Blob Storage, etc.

3. Configuration du JoliMediaBundle

📚 Pourquoi cette configuration ?

Avant de plonger dans le fichier de configuration, comprenons les concepts clés :

ConceptRôleExemple
LibraryConteneur de stockagedefault, products, avatars
OriginalFichiers uploadés (non modifiés)photo.jpg
CacheFichiers transformés/optimisésphoto-thumbnail.jpg
VariationsDifférentes versions d’un médiathumbnail, medium, large
ProcessorMoteur de traitement d’imageGD, Imagick

Le principe : Vous uploadez UN fichier original, le bundle génère automatiquement PLUSIEURS variations selon vos besoins (miniature, moyenne résolution, haute résolution…).

💡 Astuce : Séparer original et cache permet de regénérer facilement toutes les variations si vous changez les paramètres.

Créez le fichier config/packages/joli_media.yaml :

joli_media:
    # Bibliothèque par défaut
    default_library: default

    # Configuration des processeurs d'images
    processors:
        imagine:
            driver: gd  # Utiliser GD au lieu d'Imagick
            options:
                jpeg_quality: 85
                png_quality: 80
                quality: 85

    # Configuration des bibliothèques de médias
    libraries:
        default:
            # Configuration du stockage original
            original:
                flysystem: "filesystem.original.storage"
                url_generator:
                    strategy: folder
                    path: /media/original/
            
            # Configuration du stockage cache (variations)
            cache:
                flysystem: "filesystem.cache.storage"
                url_generator:
                    strategy: folder
                    path: /media/cache/
                must_store_when_generating_url: true
            
            # Désactiver la conversion automatique en WebP pour simplifier
            enable_auto_webp: false
            
            # Définition des variations d'images
            variations:
                # Vignette pour les listes
                thumbnail:
                    transformers:
                        resize:
                            width: 200
                            height: 200
                            mode: inside
                            allow_upscale: false
                
                # Format moyen pour l'affichage
                medium:
                    transformers:
                        resize:
                            width: 800
                            height: 600
                            mode: inside
                
                # Grande image
                large:
                    transformers:
                        resize:
                            width: 1920
                            height: 1080
                            mode: inside

Explication détaillée :

  • default_library : Nom de la bibliothèque par défaut à utiliser
  • processors.imagine : Configuration du processeur d’images
    • driver: gd : Utilise GD (généralement disponible par défaut avec PHP)
    • jpeg_quality, png_quality : Qualité de compression des images
  • libraries.default.original : Configuration du stockage des fichiers originaux
    • flysystem : Service Flysystem à utiliser
    • url_generator.path : Chemin public pour accéder aux fichiers
  • libraries.default.cache : Configuration du stockage des variations
    • must_store_when_generating_url: true : Génère le fichier lors de la création de l’URL
  • variations : Définit les différentes tailles d’images disponibles
    • thumbnail : Petite image (200x200px max)
    • medium : Image moyenne (800x600px max)
    • large : Grande image (1920x1080px max)
    • mode: inside : L’image est redimensionnée pour tenir dans les dimensions sans déformation

🔑 Points clés à retenir

PointCe qu’il faut comprendre
Séparation original/cacheLes fichiers originaux restent intacts, seules les variations sont générées dans le cache
Variations à la demandeAvec must_store_when_generating_url: true, les variations sont créées lors du premier accès
Mode insideL’image est réduite proportionnellement pour tenir dans les dimensions sans rogner ni déformer
Qualité d’imagejpeg_quality: 85 est un bon compromis entre qualité et taille de fichier
Driver GD vs ImagickGD est inclus avec PHP, Imagick offre plus de fonctionnalités mais nécessite une extension

⚠️ Piège courant : Si vous changez les paramètres des variations, pensez à vider le cache : symfony console cache:clear && rm -rf public/media/cache/*

4. Configuration de l’Intégration EasyAdmin

Créez le fichier config/packages/joli_media_easy_admin.yaml :

joli_media_easy_admin:
    # Configuration de l'upload
    upload:
        max_files: 10
        max_file_size: 20  # En Mo
        accepted_files:
            - image/*
            - video/*
            - application/pdf
    
    # Visibilité des fonctionnalités
    visibility:
        show_variations_stored: true
        show_variations_action_regenerate: true
        show_html_code: true
        show_markdown_code: true

Explication :

  • upload : Limites pour l’upload de fichiers
  • visibility : Fonctionnalités visibles dans l’interface
    • show_variations_stored : Affiche les variations générées
    • show_variations_action_regenerate : Bouton pour régénérer les variations
    • show_html_code : Affiche le code HTML pour intégrer l’image
    • show_markdown_code : Affiche le code Markdown

5. Configuration des Routes

Créez le fichier config/routes/joli_media.yaml :

# Routes pour JoliMediaBundle
_joli_media:
    resource: "@JoliMediaBundle/config/routes.php"

# Routes pour l'intégration EasyAdmin du MediaBundle
_joli_media_easy_admin:
    resource: "@JoliMediaEasyAdminBundle/src/Controller/"
    prefix: /admin/media

Explication :

  • La première section importe les routes du bundle principal
  • La seconde section importe les routes de l’intégration EasyAdmin avec le préfixe /admin/media

📝 Création de l’Entité Article

1. Création de l’Entité

Créez le fichier src/Entity/Article.php :

<?php

namespace App\Entity;

use App\Repository\ArticleRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use JoliCode\MediaBundle\Doctrine\Types as MediaTypes;
use JoliCode\MediaBundle\Model\Media;
use JoliCode\MediaBundle\Validator\Media as MediaConstraint;

#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;

    #[ORM\Column(type: MediaTypes::MEDIA, nullable: true)]
    #[MediaConstraint(
        allowedTypes: ['image'],
        allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
    )]
    private ?Media $image = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE)]
    private ?\DateTimeInterface $createdAt = null;

    public function __construct()
    {
        $this->createdAt = new \DateTime();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): static
    {
        $this->title = $title;
        return $this;
    }

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(string $content): static
    {
        $this->content = $content;
        return $this;
    }

    public function getImage(): ?Media
    {
        return $this->image;
    }

    public function setImage(?Media $image): static
    {
        $this->image = $image;
        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeInterface $createdAt): static
    {
        $this->createdAt = $createdAt;
        return $this;
    }
}

Points clés à comprendre :

  1. Type personnalisé : MediaTypes::MEDIA est un type Doctrine personnalisé qui stocke le chemin du média en base de données
  2. Objet Media : En PHP, vous manipulez un objet Media qui contient toutes les informations du fichier
  3. Validation : L’attribut #[MediaConstraint] valide automatiquement :
    • Le type de média (seulement des images)
    • Les formats acceptés (JPEG, PNG, GIF, WebP)

2. Création du Repository

Créez le fichier src/Repository/ArticleRepository.php :

<?php

namespace App\Repository;

use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<Article>
 */
class ArticleRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Article::class);
    }
}

🎨 Configuration d’EasyAdmin

1. DashboardController

Créez le fichier src/Controller/Admin/DashboardController.php :

<?php

namespace App\Controller\Admin;

use App\Entity\Article;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DashboardController extends AbstractDashboardController
{
    #[Route('/admin', name: 'admin')]
    public function index(): Response
    {
        return $this->render('admin/dashboard.html.twig');
    }

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            ->setTitle('JoliMediaBundle - Tutorial')
            ->setFaviconPath('favicon.ico');
    }

    public function configureMenuItems(): iterable
    {
        yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
        yield MenuItem::linkToCrud('Articles', 'fas fa-newspaper', Article::class);
        yield MenuItem::linkToUrl('Médiathèque', 'fas fa-images', '/admin?dashboardControllerFqcn=App%5CController%5CAdmin%5CDashboardController&routeName=joli_media_easy_admin_explore');
    }
}

Explication :

  • configureMenuItems() : Définit le menu de l’admin avec un lien vers la médiathèque
    • L’URL de la médiathèque doit inclure le paramètre dashboardControllerFqcn pour que le bundle ait le contexte EasyAdmin nécessaire
    • Le paramètre routeName=joli_media_easy_admin_explore indique quelle page de la médiathèque afficher
  • Pas de configureAssets() : Le JoliMediaBundle gère automatiquement ses propres assets (CSS et JS). Il n’est pas nécessaire de les ajouter manuellement dans le DashboardController, cela causerait même une erreur de duplication

2. ArticleCrudController

Créez le fichier src/Controller/Admin/ArticleCrudController.php :

<?php

namespace App\Controller\Admin;

use App\Entity\Article;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use JoliCode\MediaBundle\Bridge\EasyAdmin\Field\MediaChoiceField;

class ArticleCrudController extends AbstractCrudController
{
    public static function getEntityFqcn(): string
    {
        return Article::class;
    }

    public function configureCrud(Crud $crud): Crud
    {
        return parent::configureCrud($crud)
            // Ajouter le thème de formulaire pour le MediaBundle
            ->addFormTheme('@JoliMediaEasyAdmin/form/form_theme.html.twig')
            ->setEntityLabelInSingular('Article')
            ->setEntityLabelInPlural('Articles')
            ->setPageTitle('index', 'Liste des articles')
            ->setPageTitle('new', 'Créer un article')
            ->setPageTitle('edit', 'Modifier un article');
    }

    public function configureFields(string $pageName): iterable
    {
        yield IdField::new('id')->hideOnForm();
        
        yield TextField::new('title', 'Titre')
            ->setRequired(true)
            ->setHelp('Le titre de votre article');
        
        yield TextEditorField::new('content', 'Contenu')
            ->setRequired(true)
            ->setHelp('Le contenu complet de votre article');
        
        yield MediaChoiceField::new('image', 'Image')
            ->setRequired(false);
        
        yield DateTimeField::new('createdAt', 'Date de création')
            ->hideOnForm();
    }
}

Points importants :

  1. addFormTheme() : Active le thème de formulaire du JoliMediaBundle
    • Sans cela, le champ MediaChoiceField ne s’afficherait pas correctement
  2. MediaChoiceField : Le champ magique fourni par le bundle
    • Permet de sélectionner un média existant OU d’en uploader un nouveau
    • Affiche un aperçu de l’image sélectionnée
    • Optionnel avec setRequired(false) pour permettre des articles sans image

3. Template du Dashboard

Créez le fichier templates/admin/dashboard.html.twig :


{% extends '@EasyAdmin/page/content.html.twig' %}

{% block content_title %}
    <h1>Bienvenue dans l'administration</h1>
{% endblock %}

{% block main %}
    <div class="row">
        <div class="col-md-6">
            <div class="card mb-3">
                <div class="card-body">
                    <h5 class="card-title">
                        <i class="fas fa-newspaper"></i> Gestion des Articles
                    </h5>
                    <p class="card-text">Créez et gérez vos articles avec leurs images.</p>
                    <a href="/admin/article" class="btn btn-primary">
                        Voir les articles
                    </a>
                </div>
            </div>
        </div>
        <div class="col-md-6">
            <div class="card mb-3">
                <div class="card-body">
                    <h5 class="card-title">
                        <i class="fas fa-images"></i> Médiathèque
                    </h5>
                    <p class="card-text">Gérez vos images et médias avec JoliMediaBundle.</p>
                    <a href="/admin?dashboardControllerFqcn=App%5CController%5CAdmin%5CDashboardController&routeName=joli_media_easy_admin_explore" class="btn btn-primary">
                        Accéder à la médiathèque
                    </a>
                </div>
            </div>
        </div>
    </div>

    <div class="alert alert-info mt-4">
        <h4 class="alert-heading">À propos de ce projet</h4>
        <p>
            Ce projet est un tutoriel pédagogique démontrant l'intégration du 
            <strong>JoliMediaBundle</strong> avec <strong>EasyAdmin</strong> dans Symfony.
        </p>
        <hr>
        <p class="mb-0">
            Le bundle JoliMediaBundle facilite la gestion, le stockage et l'optimisation 
            des médias dans vos applications Symfony.
        </p>
    </div>
{% endblock %}

Explication :

  • ea_url() : Fonction Twig fournie par EasyAdmin pour générer des URLs vers les pages d’administration
    • .setController() : Spécifie le contrôleur CRUD cible (échapper les backslashes avec \\\\)
    • .setAction('index') : Définit l’action à exécuter (ici, afficher la liste)
  • Cette méthode est plus fiable que path() car elle gère automatiquement tous les paramètres EasyAdmin nécessaires

🗄️ Migration de Base de Données

1. Créer la Migration

symfony console make:migration

Cette commande génère un fichier de migration dans le dossier migrations/.

2. Exécuter la Migration

symfony console doctrine:migrations:migrate

Cela crée la table article dans votre base de données PostgreSQL.

📁 Préparation des Dossiers Médias

Créons les dossiers de stockage des médias.

mkdir -p public/media/original public/media/cache

Installation des Assets

symfony console assets:install

Cette commande copie les assets publics des bundles (CSS, JS, images) dans public/bundles/.

Test de l’Application

1. Démarrer le Serveur

symfony serve -d

2. Accéder à l’Administration

Ouvrez votre navigateur à l’adresse : https://localhost:8000/admin et tada ! ✨

✅ Conclusion

JoliMediaBundle transforme la gestion de médias dans Symfony d’un casse-tête en une fonctionnalité clé en main. En quelques minutes de configuration, vous obtenez une médiathèque prête à l’emploi.

Pour aller plus loin

Back to Blog

Comments (0)

Loading comments...

Leave a Comment