Tuto Symfony 3 :Upload d'une image en utilisant un service Upload File

La fonctionnalité Upload est très courante et incontournable pour un site dynamique .Afin de faciliter son développement  Symfony 3 offre la possibilité d'utiliser un service afin d'optimiser le code et de pouvoir réutiliser le service plusieurs fois dans différents contrôleur.Dans ce tutoriel nous allons ajouter le champ image au formulaire d'ajout d'un plat.Cet exemple est applicable sur les fichier pdf aussi et d’autres format il faut juster adapter quelque propriétés.

1-Ajouter un attribut image

La première chose à faire est d'ajouter un attribut image à l'entité Plat.Le type de cet attribut est String car il sert à contenir le nom du fichier image et non pas l'image .Ce nom nous permettra après  d’accéder à l'image téléchargée.
<?php
namespace RestoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;


//....

/**
     * @ORM\Column(type="string")
     *
     * @Assert\NotBlank(message="Ajouter une image jpg")
     * @Assert\File(mimeTypes={ "image/jpeg" })
     */
    private $image;

    public function getImage()
    {
        return $this->image;
    }

    public function setImage($image)
    {
        $this->image = $image;

        return $this;
    }
?>


l'option mimeTypes sert à préciser le type de fichier accepté dans notre cas nous demandons une image de format jpeg mais vous pouvez notamment ajouter d'autres formats .Si vous avez besoin d'ajouter un fichier pdf vous devez remplacer "image/jpeg" par "application/pdf"

visitez ce lien pour voir les mimeTypes des autre formats utilisables:
 https://www.sitepoint.com/mime-types-complete-list/

2-Ajouter un champs image au formulaire

Afin d'afficher un champ  pour ajouter une image il faut mettre le fichier PlatType.php à jour .

<?php
namespace RestoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;



class PlatType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder
            // ...
            ->add('image', FileType::class, array('label' => 'Image(JPG)'))
            // ...
        ;
    }
?>


Si votre version de php n'est pas récente 5.5 par exemple, ce code ne fonctionnera pas, dans ce cas  ajoutez le champ image comme ceci:


<?php
namespace RestoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;



class PlatType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            //..
                ->add('image', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('label' => 'Image'))
            //..
            ;
    }
?>

Il faut aussi modifer le fichier twig afin d'afficher le nouveau champ .

{{ form_start(form) }}
    {# ... #}

    {{ form_row(form.image) }}
{{ form_end(form) }}


Si vous afficher votre formulaire de cette façon

{{form_start(form)}} 
            {{form_widget(form)}}
            <input class="btn btn-success" type="submit" value="Ajouter" /> 
        {{form_end(form)}}

vous n'avez pas besoin de modifier le fichier ajoutPlat.html.twig.

3-Créer le service d'upload

Sous le répertoire RestoBundle , on créé un nouveau fichier ImageUpload.php.

<?php
namespace RestoBundle;

use Symfony\Component\HttpFoundation\File\UploadedFile;

class ImageUpload
{
    private $targetDir;

    public function __construct($targetDir)
    {
        $this->targetDir = $targetDir;
    }

    public function upload(UploadedFile $file)
    {
        $fileName = md5(uniqid()).'.'.$file->guessExtension();

        $file->move($this->getTargetDir(), $fileName);

        return $fileName;
    }

    public function getTargetDir()
    {
        return $this->targetDir;
    }
}

*   Lorsque le formulaire est téléchargé, l’attribut image contient tout le contenu du fichier jpg . Étant donné que cet attribut ne stocke que le nom du fichier, vous devez définir sa nouvelle valeur avant de persister les changements de l'entité;
Dans les applications Symfony, les fichiers téléchargés sont des objets de la classe UploadedFile. Cette classe fournit des méthodes pour les opérations sur les fichiers téléchargés;
* La classe UploadedFile fournit des méthodes pour obtenir l'extension de fichier d'origine (getExtension ()), la taille de fichier d'origine (getClientSize ()) et le nom de fichier d'origine (getClientOriginalName ()).

4-Enregistre le service dans le fichier services.yml


# app/config/services.yml
//...
resto.image_uploader:
        class: RestoBundle\ImageUpload
        arguments: ['%images_directory%']
    

5-Définir Images_directory 

Créez le répertoire dans lequel vous voulez mettre le images téléchargées sous le dossier web/ par exemple uploads/images .ensuite vous devez déclarer le paramètre images_directory en luit attribuant le chemin de ce dossier.

# app/config/config.yml
//...
parameters:
    locale: en
    images_directory: '%kernel.root_dir%/../web/uploads/images'



6-Synchronisation en utilisant  Doctrine Listener

Vous pouvez utiliser le service uploadImage à partir du contrôleur sans avoir besoin des d'un listener si vous n'avez pas besoin d'ajouter un élément à la base de données .Mais dans notre cas nous voulons ajouter un nouveau plat dans la table plat de la BDD .Doctrine Listener accomplit donc le rôle de synchronisation . Créez un dossier EventListener sous le répertoire RestoBundle. Ensuite créez une classe UploadImageListener

<?php
namespace RestoBundle\EventListener;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use RestoBundle\Entity\Plat;
use RestoBundle\ImageUpload;

class ImageUploadListener
{
    private $uploader;

    public function __construct(ImageUpload $uploader)
    {
        $this->uploader = $uploader;
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();

        $this->uploadFile($entity);
    }

    public function preUpdate(PreUpdateEventArgs $args)
    {
        $entity = $args->getEntity();

        $this->uploadFile($entity);
    }

    private function uploadFile($entity)
    {
        // upload only works for Product entities
        if (!$entity instanceof Plat) {
            return;
        }

        $file = $entity->getImage();

        // only upload new files
        if (!$file instanceof UploadedFile) {
            return;
        }

        $fileName = $this->uploader->upload($file);
        $entity->setImage($fileName);
    }
}


Déclarez le listener dans le fichier services.yml

# app/config/services.yml
//...
resto.doctrine_image_listener:
        class: RestoBundle\EventListener\ImageUploadListener
        arguments: ['@resto.image_uploader']
        tags:
            - { name: doctrine.event_listener, event: prePersist }
            - { name: doctrine.event_listener, event: preUpdate }
6-Afficher l'image ajouté

¨Enfin Pour afficher une image vous devez définir le chemin src  , dans notre il faut définir l’attribut src comme ceci:

<img src="{{ asset('uploads/images/' ~ plat.image) }}" height="100px" width="250px">

Commentaires