Compression des images produit¶
Points d’entrée¶
Lors de ProductController::create() et ProductController::update(), le champ non mappé image du formulaire ProductType est transmis à :
UploaderService::uploadImageCompressed(
$file,
$this->getParameter('product_directory')
);
Le répertoire cible est public/uploads/products/sale. Le même service est utilisé pour les images de stock d’entrepôt dans ProductStockController.
Prérequis¶
- extension PHP GD ;
- droits d’écriture sur le répertoire cible ;
- mémoire PHP suffisante pour décoder l’image originale et créer le bitmap redimensionné.
La mémoire consommée dépend surtout des dimensions décodées, pas uniquement du poids du fichier compressé.
Algorithme¶
Valeurs par défaut :
| Paramètre | Valeur |
|---|---|
| dimension maximale | 1600 px |
| qualité JPEG initiale | 82 |
| qualité minimale | 55 |
| taille cible | 2 000 000 octets |
Traitement :
- Lire le MIME détecté côté serveur.
- Générer un nom slugifié et unique terminé par
.jpg. - Décoder JPEG, PNG ou WebP avec GD.
- Calculer un facteur conservant le ratio.
- Ne réduire que si largeur ou hauteur dépasse 1600 px.
- Créer une image RGB aux nouvelles dimensions.
- Appliquer un fond blanc, puis rééchantillonner l’original.
- Encoder en JPEG progressif.
- Essayer successivement les qualités
82,78,72,66,60,55, sans doublon. - Arrêter dès que le fichier est valide et inférieur ou égal à 2 000 000 octets.
- Libérer les ressources GD et enregistrer uniquement le nom dans l’entité.
Le paramètre initial est borné entre 55 et 95. Si une autre qualité est passée, elle devient la première tentative.
Conversion et transparence¶
Toutes les images traitées sont converties en JPEG. La transparence PNG ou WebP est donc perdue et remplacée par un fond blanc. Le profil colorimétrique et les métadonnées EXIF ne sont pas explicitement conservés. L’orientation EXIF n’est pas corrigée.
Cas de repli¶
uploadFile() est utilisé sans compression lorsque :
- le MIME n’est pas une image ;
- le format n’est pas JPEG, PNG ou WebP ;
- GD ne supporte pas WebP ;
- le décodage échoue ;
- l’écriture JPEG échoue ou produit un fichier vide.
Dans ce cas, l’extension et le contenu d’origine sont conservés. Le fallback rend l’ajout plus tolérant, mais signifie que la compression et la limite cible ne sont pas garanties.
Limite actuelle de 2 Mo¶
La taille de 2 Mo est une cible, pas une validation stricte. Si le dernier JPEG de qualité 55 reste supérieur à 2 Mo, le code le conserve tout de même dès lors que le fichier existe et n’est pas vide.
Pour garantir la limite, il faudrait :
- réduire à nouveau les dimensions après la qualité minimale ; ou
- supprimer le fichier et lever une exception si la cible reste dépassée.
Validation du formulaire¶
Le champ image de ProductType est facultatif et non mappé. Il ne définit actuellement aucune contrainte Symfony File sur :
- la taille maximale de l’upload ;
- les MIME autorisés ;
- les dimensions minimales ou maximales.
La détection dans UploaderService ne remplace pas une validation de formulaire. Il est recommandé d’ajouter Image ou File, de limiter la taille originale et de rejeter explicitement les formats non supportés.
Cohérence et cycle de vie¶
- La création et la route principale de mise à jour compressent l’image.
- Certains autres flux historiques, notamment le réajustement depuis la page de détail, appellent encore
uploadFile()et peuvent stocker une image non compressée. - Le remplacement d’une image ne supprime pas automatiquement l’ancien fichier, ce qui peut produire des fichiers orphelins.
- L’upload est effectué avant le
flushDoctrine. Si la transaction métier échoue ensuite, le fichier peut rester présent sans référence en base.
Diagnostic¶
En cas d’échec :
- vérifier
ext-gdet le support du format ; - contrôler
memory_limit,upload_max_filesizeetpost_max_size; - vérifier les permissions du répertoire ;
- inspecter les dimensions de l’original ;
- vérifier si le fallback a conservé l’extension d’origine ;
- contrôler l’espace disque et les fichiers orphelins.