Avant de commencer, assurez-vous que les éléments suivants sont déjà en place dans votre projet :
Si ces prérequis sont remplis, nous pouvons maintenant explorer la mise en place de la fonctionnalité d'upload.
Nous aurons besoin du bundle VichUploader :
composer require vich/uploader-bundle
On configure le VichUploaderBundle pour notre entité Products.
# api/config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
metadata:
type: attribute
mappings:
products:
uri_prefix: /images/products
upload_destination: '%kernel.project_dir%/public/images/products'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
Puis API Platform en activant globalement le format Multipart.
#config/packages/api_platform.yaml
api_platform:
formats:
multipart: ['multipart/form-data']
jsonld: ['application/ld+json']
Nous allons prendre l'exemple de l'ajout d'une image pour un produit.
L'entité Products
devra posséder au moins les champs suivants :
id
: l'identifiant du produitname
: le nom du produitimageName
: le nom de l'image (celle qui sera stockée sur le serveur)updatedAt
: indispensable pour le VichUploaderBundleCommençons par lui ajouter un attribut PHP #[Vich\Uploadable]
pour indiquer que la classe Products est uploadable dans le contexte du VichUploaderBundle
use Vich\UploaderBundle\Mapping\Annotation as Vich;
#[Vich\Uploadable]
class Products
{
// ...
}
Ensuite, nous aurons besoin de lui ajouter une autre propriété indispensable au bon fonctionnement du bundle :
imageFile
accompagnée de ses getters/setters. Cette propriété servira à stocker l'objet UploadedFile après la soumission du formulaire. Ce champ n'est pas une colonne de la table Products.Une propriété updatedAt
est indispensable pour notifier Doctrine des modifications de fichier, déclencher les événements nécessaires et assurer une gestion synchronisée entre la base de données et le système de fichiers uploadés.
# src/Entity/Products.php
//...
use Symfony\Component\HttpFoundation\File\File;
//...
class Products
{
//...
#[Assert\File(
maxSize: '5M',
maxSizeMessage: 'Seules les images dont la taille est inférieure à 5 Mo sont acceptées.',
mimeTypes: ['image/jpeg','image/png','image/webp'],
mimeTypesMessage: 'Seules les images aux formats JPEG, PNG ou WEBP sont autorisées.'
)]
#[Vich\UploadableField(mapping: 'products', fileNameProperty: 'imageName')]
private ?File $imageFile = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
//...
public function setImageFile(?File $imageFile = null): void
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getImageFile(): ?File
{
return $this->imageFile;
}
Nous pouvons effectuer la migration
symfony console make:migration
symfony console d:m:m
La classe MultipartDecoder agit comme un pont pour extraire et combiner les paramètres et fichiers d'une requête multipart, en les renvoyant sous forme de tableau prêt à être utilisé dans le processus de désérialisation.
// src/Encoder/MultipartDecoder.php
namespace App\Encoder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
final class MultipartDecoder implements DecoderInterface
{
public const FORMAT = 'multipart';
public function __construct(private readonly RequestStack $requestStack)
{
}
public function decode(string $data, string $format, array $context = []): ?array
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) return null;
return $request->request->all() + $request->files->all();
}
public function supportsDecoding(string $format): bool
{
return self::FORMAT === $format;
}
}
La classe UploadedFileDenormalizer
vérifie si les données à dénormaliser sont des fichiers, autrement dit des instances de la classe File. Si c'est le cas, elle les retourne telles quelles.
// src/Serializer/UploadedFileDenormalizer.php
namespace App\Serializer;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
final class UploadedFileDenormalizer implements DenormalizerInterface
{
public function denormalize($data, string $type, string $format = null, array $context = []): File
{
return $data;
}
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
{
return $data instanceof File;
}
public function getSupportedTypes(?string $format): array
{
return [File::class => true,];
}
}
Avant de lancer le serveur et d'envoyer notre requête, on va simplement ajouter un groupe de sérialisation aux propriétés pour définir quelles données seront incluses dans la réponse et surtout, ajouter le format multipart :
use Symfony\Component\Serializer\Attribute\Groups;
//...
#[Post(
normalizationContext: ['groups' => ['products:read']],
)]
#[Vich\Uploadable]
class Products
{
//...
#[ORM\Column(length: 255)]
#[Groups(['products:read'])]
private ?string $name = null;
#[ORM\Column(length: 255, nullable: true)]
#[Groups(['products:read'])]
private ?string $imageName = null;
//...
Lançons le serveur
symfony serve -d
On utilise un outil comme Postman ou Insomnia pour envoyer une requête POST à cette adresse :
http://localhost:8000/api/products
Et avec les paramètres suivants dans l'onglet body/form-data(multipart)
Key | Value | Type |
---|---|---|
name | monProduit | Text |
imageFile | [Votre fichier] | File |
Sans oublier de préciser le format de réponse attendu dans le Header
Key | Value | Type |
---|---|---|
Accept | application/ld+json | Text |
Et voilà ! Nous avons bien reçu une réponse 201 du serveur, confirmant le succès de l'opération, avec le nom de l'image uploadée retourné en réponse.
Mettre en place un système d’upload d’une seule image avec Symfony, API Platform et le VichUploaderBundle est à la fois simple et efficace. Cette approche permet d’ajouter rapidement une fonctionnalité essentielle tout en garantissant une gestion performante et sécurisée des fichiers. En suivant les étapes décrites, il devient facile de répondre à des besoins courants sans complexité superflue, offrant ainsi une base solide pour évoluer vers des scénarios plus complexes à l’avenir.
Documentation officielle d’API Platform, de Symfony et du VichUploaderBundle.