Uploader proprement des images avec Symfony

.........................................................

L’utilisation du framework Symfony permet d’accélérer le développement d’un projet et facilite son maintient dans le temps. Oui, mais à condition de s’imposer une certaine rigueur. En effet, bien qu’imposant un cadre déjà assez stricte (comporé au Zend Framework par exemple), il est possible de se retrouver, si on travaille « à l’arrache », avec une montagne de code crado…

L’upload n’est pas une chose difficile à mettre en place avec Symfony. Mais qu’en est-il si l’on souhaite le faire tout en conservant la séparation des différentes couches MVC ? Je vais donc vous dévoiler en exclusivité ma méthode ! :p

Objectif de l’Article

Le but de cet article est de vous présenter ma technique concernant l’upload de fichiers et plus particulièrement l’upload d’images avec Symfony. Pour ce faire, nous allons imaginer la situation qui suit.

Vous êtes en train de développer une application intranet pour une grosse société. Il vous a été demandé d’y ajouter un trombinoscope afin de pouvoir plus facilement mettre un visage sur le nom de chaque employé.

Ok, très bien, mais l’application en question est non seulement accessible via le navigateur, mais aussi via une application de bureau Flex. Il est donc absolument nécessaire que vous sépariez bien les différentes couches du projet.

Commencons donc par notre modèle…

Le Schéma de la Table

Tout d’abord, le schéma de la base de données. Voici à quoi pourrait ressembler la table des employés avec leur photo :

  1.  
  2.   Employe:
  3.     columns:
  4.       nom:      string(100)
  5.       prenom:   string(100)
  6.       photo:    string(100)
  7.  

Bon ok, c’est un peu simplifié, mais ça suffira pour ce que nous voulons voir ! ;)

Comme vous l’avez sans doute remarqué, le champ photo est une chaine de caractères. Et oui, nous allons juste y stocker le nom du fichier puisque toutes les photos seront dans un même dossier.

Le Formulaire

Par défaut, le formulaire généré ne comporte ici que des champs de type texte.
Nous allons donc devoir modifier le formulaire pour remplacer le champ « photo » par un champ de type fichier (on va utiliser le widget sfWidgetFormInputFileEditable ainsi que son validateur associé (le validateur sfValidatorFile.

Voici donc la surcharge de la méthode « configure » de notre classe « EmployeForm » :

  1.  
  2. public function configure()
  3. {
  4.   parent::configure();
  5.  
  6.   // Champ fichier pour la photo
  7.   $this->widgetSchema[‘photo’] = new sfWidgetFormInputFileEditable(array(
  8.     ‘file_src’     => $this->getObject()->getPhotoFileSrc(),
  9.     ‘is_image’     => true,
  10.     ‘with_delete’  => !is_null($this->getObject()->getPhotoFileSrc())
  11.   ));
  12.  
  13.   // Validateur fichier
  14.   $this->validatorSchema[‘photo’] = new sfValidatorFile(array(
  15.     ‘required’    => false,
  16.     ‘mime_types’  => ‘web_images’,
  17.     ‘path’        => $this->getObject()->getPhotoDirPath()
  18.   ));
  19.  
  20.   // Validateur pour la suppression du fichier
  21.   $this->validatorSchema[‘photo_delete’] = new sfValidatorPass();
  22. }
  23.  

Comme vous l’avez remarqué, j’ai utilisé deux accesseurs de l’objet qui ne sont pas générées par défaut (getPhotoFileSrc et getPhotoDirPath), nous verrons leur implémentation par la suite.

N.B. Il ne faut pas oublier d’ajouter le validateur « photo_delete » car sinon, le validateur vous nous envoyer paitre lorsqu’on va vouloir supprimer la photo. En effet, le widget sfWidgetFormInputFileEditable rajoute une checkboxe pour supprimer l’éventuel fichier actuel.

Et voilà ! :)

On va pouvoir maintenant passer à notre modèle…

Le Modèle

Le modèle n’est pas très compliqué mais il y a tout de même un point sur lequel j’aimerais attirer votre attention.
Si vous supprimez ou si vous remplacez l’image actuelle via le formulaire, ce dernier se contentera de modifier la valeur du champ « photo ». Les photos obsolètes vont alors s’amasser et utiliser de l’espace disque inutilement !
D’un autre côté, ce n’est pas le rôle du formulaire de supprimer le fichier… Et oui, c’est au modèle de gérer ça car souvenez-vous, l’application proposera aussi une interface Flex qui possédera sont propre formulaire en Flex. Et si vous vous basez sur le formulaire pour supprimer les anciennes photos, ça fonctionnera peut-être pour votre application « classique » mais pas sur celle en Flex.

Donc voici un petit récapitulatif de ce que nous allons devoir implémenter dans notre modèle (ici la classe « Employe ») :

  • La méthode getPhotoDirPath qui retourne le chemin absolu vers le dossier de photos
  • La méthode getPhotoFilePath qui retourne le chemin absolu vers le fichier de la photo
  • La méthode getPhotoDirSrc qui retourne le chemin relatif vers le dossier de photos
  • La méthode getPhotoFileSrc qui retourne le chemin relatif vers le fichier de la photo
  • La suppression des anciennes images

Voici donc pour commencer nos quelques accesseurs :

  1.  
  2. public function getPhotoFileSrc()
  3. {
  4.   if (!$this->getPhoto())
  5.   {
  6.     return null;
  7.   }
  8.  
  9.   return  $this->getPhotoDirSrc() . ‘/’ . $this->getPhoto();
  10. }
  11.  
  12. public function getPhotoFilePath()
  13. {
  14.   if (!$this->getPhoto())
  15.   {
  16.     return null;
  17.   }
  18.  
  19.   return  $this->getPhotoDirPath() . ‘/’ . $this->getPhoto();
  20. }
  21.  
  22. public function getPhotoDirSrc()
  23. {
  24.   return ‘/uploads/trombi’;
  25. }
  26.  
  27. public function getPhotoDirPath()
  28. {
  29.   return sfConfig::get(‘sf_upload_dir’) . ‘/trombi’;
  30. }
  31.  

Pour ce qui est de la suppression des anciennes images, nous allons utiliser les événements « preSave » et « postDelete » fournis par doctrine. Le premier correspond au moment avant la sauvegarde de l’enregistrement et le second au moment juste après sa suppression.

Nous allons donc distinguer deux cas : la modification ou suppression de la photo d’un employé et la suppression de l’employé (même si on espère qu’il sera inutile !).

Surcharge de la méthode preSave

La surcharge de la méthode preSave peut se faire comme ceci :

  1.  
  2. public function preSave($event)
  3. {
  4.   // On récupère le tableau des champs modifiés.
  5.   // Le premier paramètre permet de récupérer les anciennes valeurs
  6.   $modified = $event->getInvoker()->getModified( true );
  7.  
  8.   // On regarde si le champ photo a été modifié
  9.   if (isset($modified[‘photo’]))
  10.   {
  11.     $old_path = $this->getPhotoDirPath() . ‘/’ . $modified[‘photo’];
  12.  
  13.     // Si le fichier existe, on le supprime
  14.     if (file_exits($old_path))
  15.     {
  16.       @unlink($old_path);
  17.     }
  18.   }
  19. }
  20.  

Le principe est simple, avant d’enregistrer, on récupère la liste des champs modifiés et on regarde si le champ « photo » en fait partie. Si oui, on regarde si l’ancienne valeur correspond à une image qu’on supprime alors.

Surcharge de la méthode postDelete

La surcharge de la méthode postDelete est plus simple encore :

  1.  
  2. public function postDelete($event)
  3. {
  4.   // On supprime la photo si le fichier existe
  5.   if (file_exists($event->getInvoker()->getPhotoFilePath()))
  6.   {
  7.     @unlink($event->getInvoker()->getPhotoFilePath());
  8.   }
  9. }
  10.  

Comme vous pouvez le voir, il s’agit uniquement de supprimer le fichier si il existe.

Et voilà ! Notre modèle est prêt. Vous voyez, ce n’était pas compliqué :)

Je vous laisse vous débrouiller pour la vue et le controlleur mais normalement un simple « doctrine:generate-admin intranet Employe » suffit !

Conclusion

Comme vous l’avez vu, la méthode n’est vraiment pas compliquée. Cependant, libre à vous de l’adopter et de vous en inspirer pour faire encore mieux !

J’attends vos questions et avis sur la question ! :D

Lire la suite

Archives par Mois

Rechercher dans ce Site


[]