22 - Symfony : Upload de médias avec Vich Upload !
Objectifs
- ✅ Installer et configurer le bundle VichUploader dans un projet Symfony
- ✅ Permettre à un utilisateur d’uploader un fichier sur un serveur depuis une application.
- ✅ Paramétrer l'entité
- ✅ Paramétrer le formulaire
- ✅ Persister les données
Introduction
User stories 📋
en tant que visiteur, je souhaite me connecter
en tant que visiteur, je souhaite avoir accès à un formulaire d'inscription afin de pouvoir me créer un compte
In progress 🤸
en tant que visiteur, je dois voir un visuel pour chaque acteur afin de pouvoir identifier plus facilement quelqu'un sans avoir à lire son nom.
en tant que visiteur, je dois voir un visuel pour chaque série afin de pouvoir identifier plus facilement un programme sans avoir à lire le titre.
en tant qu'administrateur, je dois voir un champ fichier depuis la page de création d'un programme afin de pouvoir téléverser un visuel.
Doing 🏗️
mise de l'upload de fichier pour l'administrateur
Done ✅
en tant que que visiteur, je veux pouvoir accéder à la page d'un épisode via une URL contenant son titre
en tant que que visiteur, je veux pouvoir accéder à la page d'un acteur via une URL contenant son nom
en tant que que visiteur, je veux pouvoir accéder à la page d'une série via une URL contenant son titre
Dans cette quête, tu verras comment uploader des images ou fichiers de manière générale (pdf, etc.) sur ton site Wild Series. Cela te permettra, dans tes formulaires, de charger ces fichiers au travers d'un champ et de les conserver sur le disque dur du serveur.
Pour l’instant, quand tu souhaites associer une affiche à une série, tu dois la trouver sur internet, récupérer l’url, et la saisir dans un champ. Ce n’est pas la manière la plus optimisée de gérer les médias. Cela demande à l’utilisateur de savoir faire cette manipulation, Même pour quelqu’un qui sait faire, cette opération est fastidieuse à répéter, Tu n’as pas la main sur les images venant d’une source tierce. Si celles-ci sont supprimées ou modifiées, tu en subis directement les conséquences. L’idéal est donc de conserver et maîtriser ses propres médias.
Sommaire
Installer et configurer le bundle VichUploader dans un projet Symfony
Dans cette quête, nous allons associer une image à un programme.
Il est tout à fait possible de coder manuellement l'upload de fichier dans Symfony. La documentation recommande aussi d'avoir recours au bundle le plus utilisé pour mettre en place cette fonctionnalité et qui se nomme VichUploaderBundle.
Selon la documentation de VichUploader, pour installer ce bundle, utilise Composer :
composer require vich/uploader-bundle
Symfony Flex s'execute pour générer les fichiers de configuration du bundle. Si tu as la question :
Do you want to execute this recipe?
tu dois répondre "yes".
Tu peux constater qu'une configuration de base a été ajoutée dans le fichier config/packages/vich_uploader.yaml :
vich_uploader:
db_driver: orm
#mappings:
# products:
# uri_prefix: /images/products
# upload_destination: '%kernel.project_dir%/public/images/products'
La première étape consiste à modifier cette configuration afin de spécifier le dossier dans lequel seront conservés les futurs fichiers que tu chargeras.
Une bonne pratique est de stocker ces fichiers dans le dossier public/ de ton application. C'est le seul dossier qui est exposé sur ton serveur.
Crée ensuite un sous-dossier uploads/ dans ton dossier public/. Ce dernier contiendra les fichiers chargés.
Pour simplifier la configuration, ajoute un paramètre upload_directory dans ton fichier config/services.yaml.
Ce dernier fait référence à ton dossier /images/posters dans public et te sera utile pour la configuration de Vich.
# config/services.yaml
parameters:
upload_directory: '%kernel.project_dir%/public/uploads/images/posters/'
Attention, le répertoire va contenir des images des utilisateurs. Celles-ci ne doivent pas être versionnées (au risque de se retrouver publiquement sur GitHub). Pense à mettre à jour ton .gitignore en conséquence.
Il faut maintenant indiquer à VichUploaderBundle, via son fichier de configuration (config/packages/vich_uploader.yaml), la référence vers ce dossier :
Décommente la ligne "mappings" et celles qui suivent, afin d'avoir :
# config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
poster_file:
# alias qui nous permet d'accéder à nos images depuis le navigateur
uri_prefix: /uploads/images/posters
upload_destination: '%upload_directory%'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
En spécifiant upload_directory entre %, symfony va récupérer la valeur du paramètre du même nom que tu as défini plus haut.
Ici, poster_file détermine simplement le nom de notre mapping. Il sera utile plus tard lors de la configuration des entités.
Ensuite, tu constateras qu'une ligne de configuration namer a été également ajoutée. Elle spécifie la stratégie qu'utilise Vich pour renommer les fichiers uploadés (car tu ne peux pas conserver le nom d'origine du fichier sur la machine de l'utilisateur au risque d'avoir des collisions de nom). La stratégie SmartUniqueNamer est un mélange entre le nom d'origine et une valeur aléatoire, mais tu peux utiliser d'autres stratégies de nommage si tu le souhaites.
Enfin, ajoute la clé metadata comme ci-dessous pour spécifier que la configuration se fera via les attributs PHP.
# config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
metadata:
type: attribute
mappings:
poster_file:
# alias qui nous permet d'accéder à nos images depuis le navigateur
uri_prefix: /uploads/images/posters
upload_destination: '%upload_directory%'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
Permettre à un utilisateur d’uploader un fichier sur un serveur depuis une application.
Paramétrer l'entité
Maintenant que ton bundle est correctement configuré, il faut modifier les entités de ton application pour qu'elles utilisent notre bundle.
Pour ce faire, modifie l'entité Program afin qu’elle corresponde au code ci-dessous :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
//Ici on importe le package Vich, que l’on utilisera sous l’alias “Vich”
use Vich\UploaderBundle\Mapping\Annotation as Vich;
#[ORM\Entity(repositoryClass: ProgramRepository::class)]
#[Vich\Uploadable]
class Program
{
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $poster = null;
// On va créer un nouvel attribut à notre entité, qui ne sera pas lié à une colonne
// Tu peux d’ailleurs voir que l’attribut ORM column n’est pas spécifié car
// On ne rajoute pas de données de type file en bdd
#[Vich\UploadableField(mapping: 'poster_file', fileNameProperty: 'poster')]
private ?File $posterFile = null;
La propriété $poster, qui était déjà présente, stocke le nom du fichier (donc une chaîne en string) et la propriété $posterFile stocke le contenu du fichier.
Dans notre cas, la donnée obtenue dans la variable $posterFile ne sera jamais stockée en base de données. L'idée sera de récupérer uniquement le nom du fichier uploadé (repense à ta quête “Laisse pas traîner ton file”) et le transmettre à l’attribut $poster.
Tu peux d’ailleurs voir dans la documentation de la propriété $posterFile qu’un attribut de type Vich est écrite, avec le mapping correspondant au nom que tu lui as attribué plus tôt, et un fileNameProperty qui réfère à ta propriété $poster.
Ensuite, tu vas générer les getters et setters de cette propriété.
Tu noteras que le setter de $posterFile n'est pas généré. C'est normal, car il ne s'agit pas d'un attribut mappé via Doctrine à la base de données.
Nous rajouterons donc les méthodes suivantes à la classe Program.
public function setPosterFile(File $image = null): Program
{
$this->posterFile = $image;
return $this;
}
public function getPosterFile(): ?File
{
return $this->posterFile;
}
Paramétrer le formulaire
Afin de pouvoir uploader une image via notre interface,
modifions le formulaire Form/ProgramType :
$builder
->add('title')
// ...
->add('posterFile', VichFileType::class, [
'required' => false,
'allow_delete' => true, // not mandatory, default is true
'download_uri' => true, // not mandatory, default is true
]);
(pense à préciser l'import de namespace en haut de ta classe, via : use Vich\UploaderBundle\Form\Type\VichFileType;)
De plus, l'upload étant maintenant géré par un input de type file via Vich, tu dois donc enlever la ligne ->add('poster', UrlType::class) que tu utilisais jusqu'à maintenant pour afficher une image.
Essayons maintenant d'accéder au formulaire via le navigateur. Inspecte le code source HTML :
Si tout s'est bien déroulé, tu constates :
- Visuellement, qu'un champ a été ajouté (avec un bouton "parcourir" pour sélectionner le fichier) ;
- Repère ce champ dans le code de la page :html
<input type="file" id="program_posterFile_file" name="program[posterFile][file]"> - Que la balise du formulaire dispose désormais d'un attribut
enctype="multipart/form-data"et ceci est indispensable pour pouvoir envoyer un fichier.
Essaye maintenant de modifier ce programme en modifiant l’image que tu souhaites lui attribuer.
Après validation, tu constates amèrement que ton image n’a pas été modifiée…. Mince !

En effet, si tu parcours à nouveau ton entité Program, tu te rends compte que la propriété $posterType n’est volontairement pas liée à une colonne de ta table, et n’est donc pas liée à Doctrine.
Par conséquent, lorsque tu essaies de modifier ton programme juste en changeant l’image, tu es confronté au problème de non persistance des objets. Lorsque tu modifies un attribut non lié à Doctrine, ce dernier ne voit aucune nouvelle donnée apportée à son schéma (dans le persist()) et ne modifie donc pas tes changements en base de données.
Pour parer à cela, nous allons ajouter (si ce n’est déjà fait) un attribut à ton entité Program :
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DatetimeInterface $updatedAt = null;
Tu l’auras compris, le but de la manoeuvre est de modifier au même moment que l’on change ou charge une image, la date de modification qui, elle, sera persistée en base de données et qui, par conséquent, sauvegardera tous tes changements.
Génère les getters et setters de ta propriété $updatedAt (si tu ne l’as pas créé en ligne de commande) et ajoute dans le setter de ton $posterFile le code suivant :
public function setPosterFile(File $image = null): Program
{
$this->posterFile = $image;
if ($image) {
$this->updatedAt = new DateTime('now');
}
return $this;
}
Tu constates ainsi, en répétant ta manipulation d’upload, que tout fonctionne parfaitement. L’ancienne image a été supprimée de ton serveur et la nouvelle a pris le relais. Pense à donner des noms uniques à tes images afin d’éviter les conflits à l’intérieur de ton dossier d’upload.
Validation des fichiers
Voilà ! Le tour est joué ! Tu peux maintenant uploader autant de documents et images que tu souhaites sur chacune de tes entités. Mais généralement, tu souhaites contraindre l'utilisateur à certains types de fichiers, ou à un poids maximal (pour rappel, la configuration de base de PHP n'autorise pas d'uploads de fichiers de plus de 2M). De plus, si ton uploads sert à afficher une image, il ne faut pas que l'utilisateur puisse envoyer un fichier PDF ou un document texte. Pour faire ces vérifications, tu vas utiliser les attributs #[Assert] (que tu connais déjà), plus particulièrement celles de type File, afin de restreindre les types Mime et le poids des fichiers que l’utilisateur peut envoyer.
#[Vich\UploadableField(mapping: 'poster_file', fileNameProperty: 'poster')]
#[Assert\File(
maxSize: '1M',
mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
)]
private ?File $posterFile = null;
Dans cet exemple, la taille maximale autorisée sera 1M et uniquement pour des fichiers de type jpeg, png ou webp.

Affichage des images
Maintenant que ton uplaod fonctionne, comment afficher les images du dossier uploads ? Dans ton Twig, tu pourrais retrouver le chemin vers l'images et l'appeler, mais Vich t'apporte une fonction dédiée pour simplifier les choses. Il suffit de mettre le code suivant
<img src="{{ vich_uploader_asset(program) }}" alt="{{ program.title }}">
Tu passes l'objet "program" en paramètre de vich_uploader_asset. Vich retrouve le mapping configuré dans Program et peut donc en déduire le chemin vers le répertoire où est stockée la ressource. Des options avancées existent pour le cas où l'entité aurait plusieurs mapping (ce n'est pas le cas ici), n'hésite pas à regarder la documentation si besoin.
💪 Challenge
Associer une image à une entité
Maintenant que tu sais comment te servir de Vichuploader et que tes séries TV sont représentées par un poster, allons plus loin ! À toi de permettre à l'administrateur d'également ajouter une photo à un acteur.
Évidemment, les visuels que tu ajoutes en base doivent être visibles dans ton application sur les différentes pages qui affiches des séries et acteurs. À toi de modifier les vues en conséquences !

Critères de validation
- VichUploaderBundle est correctement configuré
- L’ajout ainsi que l’update d’image fonctionnent parfaitement.
- Tes images possèdent un nom unique.
- Le code est disponible sur un repository GitHub, avec une branche correspondant à cette quête.
Ressources partagées sur cette quête
- Des compléments intéressants, notamment concernant l'utilisation des attributs ;)https://github.com/dustin10/VichUploaderBundle/blob/master/docs/usage.md