---
title: "JoliMediaBundle : Enfin un gestionnaire de médias moderne et efficace pour Symfony"
excerpt: "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."
publishDate: 2025-11-22T00:00:00.000Z
tags: ["symfony", "joli-media-bundle", "gestionnaire-de-medias", "easyadmin"]
canonical: "https://yoandev.co/joli-media-bundle"
---

## 📖 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 :

```bash
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

```bash
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

```bash
composer require easycorp/easyadmin-bundle
```

### 3. Installation d'Imagine

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

```bash
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
<?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` :

```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 :

| Concept | Rôle | Exemple |
|---------|------|---------|
| **Library** | Conteneur de stockage | `default`, `products`, `avatars` |
| **Original** | Fichiers uploadés (non modifiés) | `photo.jpg` |
| **Cache** | Fichiers transformés/optimisés | `photo-thumbnail.jpg` |
| **Variations** | Différentes versions d'un média | `thumbnail`, `medium`, `large` |
| **Processor** | Moteur de traitement d'image | GD, 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` :

```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

| Point | Ce qu'il faut comprendre |
|-------|--------------------------|
| **Séparation original/cache** | Les fichiers originaux restent intacts, seules les variations sont générées dans le cache |
| **Variations à la demande** | Avec `must_store_when_generating_url: true`, les variations sont créées lors du premier accès |
| **Mode `inside`** | L'image est réduite proportionnellement pour tenir dans les dimensions sans rogner ni déformer |
| **Qualité d'image** | `jpeg_quality: 85` est un bon compromis entre qualité et taille de fichier |
| **Driver GD vs Imagick** | GD 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` :

```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` :

```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
<?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
<?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
<?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
<?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` :

```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

```bash
symfony console make:migration
```

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

### 2. Exécuter la Migration

```bash
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.

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

## Installation des Assets

```bash
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

```bash
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

- 📖 [Documentation officielle](https://mediabundle.jolicode.com/)
- 🔗 [GitHub du projet](https://github.com/JoliCode/MediaBundle)
- 📝 [Article de blog JoliCode](https://jolicode.com/blog/jolimediabundle-un-nouveau-bundle-de-medias-pour-vos-projets-symfony)
