Symfony: Personnaliser le nom du fichier lors d’un upload avec sfWidgetFormInputFileEditable

system-software-update On emploie tous je pense, assez couramment maintenant, le widget sfWidgetFormInputFileEditable qui permet de rajouter quelques fonctionnalités à un widget d’upload classique, en l’occurrence visualisation et suppression. Le souci que j’ai rencontré récemment, c’est par contre la personnalisation du nom du fichier ainsi généré.

En effet, par défaut celui-ci est une empreinte sha1 généré aléatoirement, ce qui convient pour la majeure partie des cas, mais parfois ne suffit pas. Mais là encore Symfony étonne par le mécanisme mis en place pour contourner cette problématique, encore faut-il le savoir malheureusement.

Nous allons prendre un cas classique, une class Medias avec un champ path qui stockera le nom du fichier ainsi uploadé:

class MediasForm extends BaseMediasForm
{
  public function configure()
  {    
    $this->widgetSchema['path'] = new sfWidgetFormInputFileEditable(array(
      'file_src' => '/uploads/medias/'.$this->getObject()->getPath(),
      'is_image' => true,
      'edit_mode' => !$this->isNew(),
      'with_delete' => true,
    ));
 
    $this->validatorSchema['path'] = new sfValidatorFile(array(
    	'required' => false,
    	'path' => sfConfig::get('sf_upload_dir').'/medias',
        'mime_types' => 'web_images'
    ));
 
    $this->validatorSchema['path_delete'] = new sfValidatorBoolean();
  }

Maintenant, si on veut avoir la main sur le nom du fichier généré, il suffit tout simplement dans la class Medias de rajouter la méthode suivante:

// Ou Path est le nom du champ ciblé lors de l'upload
  public function generatePathFilename(sfValidatedFile $file)
  {
   // On a maintenant accès à notre fichier, on peut donc lui donner un nom basé sur son id ou son slug ou tout autre chose.
    return $this->getId().$file->getExtension($file->getOriginalExtension());
  }

Tout simplement! C’est ce genre de détails qui me font apprécier la constante découverte de ce framework ;)

Tags: , ,

A propos de l'auteur

Développeur web spécialisé Symfony, il est avant tout passionné de web tout simplement. Il aime les défis et farfouiller dans le code de Symfony ou Doctrine. Fondateur du blog, il exerce chez Autrement.

Vous avez aimé ce billet? Faites le savoir!

  • Delicious
  • Twitter
  • Technorati Favorites
  • FriendFeed
  • Google Bookmarks
  • Share

15 Réponses

  1. BEN 30 juillet 2009 à 16 h 33 min #

    Il y a juste un problème c’est qu’au moment de la création, l’objet est instancié mais la méthode « generatePathFilename » est appelée avant les setters. Ce qui signifie que la méthode « getId() » (ou n’importe quel getter propre à l’objet) ne retourne rien.
    Si quelqu’un connaît une solution à ce problème, je suis preneur.

  2. Tim 30 juillet 2009 à 18 h 58 min #

    Il suffit de récupérer directement l’id par exemple par sa propriété j’imagine, genre

    $this->id
    

    Je testerai ça demain

  3. BEN 31 juillet 2009 à 9 h 56 min #

    Cela revient au même, les attributs de l’objet n’ont pas encore de valeur à cette étape de la création.

  4. Tim 31 juillet 2009 à 11 h 59 min #

    Après test, effectivement mon exemple était pas vraiment bien trouvé.
    Comme je m’en sers dans un sous formulaire, mon objet Media à quand même accès à la relation et je récupère une propriété de cette relation pour la création de mon nom.

    De ce que j’ai vu, comme le generatePathFilename est appelé au moment du processValues du form, je vois pas vraiment comment contourner ce problème.

    Je ferais des corrections ce week end. Merci

  5. drewprod 2 août 2009 à 18 h 15 min #

    Faut étendre la fonction doSave() dans le *Form.class.php pour avoir accès à l’objet.

    public function doSave($con = null)
    {
    $variable = $this->getValue(‘variable’);
    $this->autreVariable = $variable->generatePathFilename($this); // pas sûr du $this
    $variable->save($pathToSave().$this->autreVariable);
    // suite du traitement
    parent::doSave($con);
    // ou return parent::doSave();
    }

    Ne pas oublier aussi d’étendre la fonction updateObject() pour la repopulation de l’objet.

    public function updateObject($values = null)
    {
    $object = parent::updateObject($values);
    $object->setVariable($this->autreVariable);
    return $object;
    }

    Bien sûr, on déclare la variable $autreVariable :
    private $autreVariable;

    J’utilise ce moyen pour avoir accès au fichier uploader et changer le nom.

    Si cela peut aider. :) ++

  6. drewprod 2 août 2009 à 18 h 23 min #

    Aussi, j’utilise la fonction generateFilename() et non generatePathFilename().

    ++

  7. Tim 2 août 2009 à 20 h 16 min #

    Le principe était justement de pas avoir à surcharger les méthode save et update

    Mais merci de ta participation!

  8. Sebastien 4 août 2009 à 18 h 38 min #

    A quel endroit dans le code peut-on voir que ton « generatePathFilename(sfValidatedFile $file) » est appelé ?

    Je pensais que ça venait de « sfValidatedFile » qui renvoyé « $this » si la fonction existe sur l’objet, mais je n’ai pas trouvé…

  9. Tim 4 août 2009 à 18 h 56 min #

    En fait la méthode est déterminé dans le saveFile de sfFormDoctrine (ou sfFormPropel). C’est d’ailleurs la raison pourquoi l’object n’est pas encore hydraté lors de l’appel.

  10. SilentBob 6 août 2009 à 10 h 30 min #

    Merci pour cette astuce qui marche impecc. Juste peut-être préciser pour les boulets dans mon genre (:D) que la méthode generate%Filename est a rajouter dans la class de base (Media ici) et non pas dans la class MediaForm. Également, le nom du champ est « camelizé ». Dans mon cas, pour le champ cpn_logo_url, ca donnait generateCpnLogoUrlFilename().

  11. Tim 6 août 2009 à 10 h 48 min #

    Pour la première remarque, je l’ai bien précisé :p Je vais le mettre en gras du coup ;)

    Bon point pour le 2e!

    Merci pour ces précisions

  12. Sebastien 6 août 2009 à 12 h 16 min #

    Est-ce qu’il y a un moyen pour que la fonction « generatePathFilename() » soit appelée à chaque modification du formulaire ?

    Si par exemple on utilise ta méthode et qu’on avait en fait accès au label.
    Déjà il faut que le label soit unique dans la base pour ne pas avoir de conflit avec les noms de fichier.

    Je créé donc mon Media1 avec son fichier, j’aurais donc « upload/media/Media1.png ».
    Maintenant, imaginons que j’edite Media1 en Media2, le nom du fichier ne changera pas..
    Pour finir je créé un nouveau Media1 (disponible vu que l’ancien s’appele maintenant Media2), dans ce cas mon fichier « upload/media/Media1.png », lié à Media2, sera écrasé par un nouveau « Media1.png » qui lui fait référence au nouveau Media1.
    Au final j’ai donc un gros problème car Media1 et Media2 renvoie au même fichier ce qui ne devrait pas être le cas.

    J’aurais la solution de changer le nom du fichier dans le repertoire si le label est modifié mais c’est pas terrible je trouve…

    Si quelqu’un a une idée…

  13. Tim 6 août 2009 à 14 h 27 min #

    En fait, la méthode n’est appelée que si tu upload un nouveau fichier.
    Sinon, il faudra passer par qqch comme l’a suggéré drewprod plus haut en surchargant le doSave de ton formulaire.

  14. Sebastien 6 août 2009 à 15 h 58 min #

    En fait j’ai réussi à le faire sans surcharger doSave et updateObject, mais j’ai un problème qui persiste, le fait de changer le nom du fichier si le label (ou autre variable utilisé pour définir le nom du fichier) change…

    Pour ça je créé une fonction static (un slugify améliorer en fait), je créé la fonction « generatePathFilename » dans l’objet et je surcharge le processForm en y ajoutant l’appel à ma fonction static.
    Ca me donne un résultat plutôt propre, mais le problème du changement de label se pose…

    De plus je me suis rendu compte que j’avais un problème avec une partie du slugify de Jobeet (iconv), si quelqu’un a une idée :
    http://forum.symfony-project.org/index.php/m/83282/#msg_83282

    Je vais continuer à chercher pour l’update… :)

  15. kbsali 22 novembre 2011 à 17 h 07 min #

    Top! :) C’etait exactement ce que je cherchais, alors Merci!!!


Laisser un message