Bonjour à tous,

J'ai besoin d'une aide parce que je sèche complètement sur un problème d'upload de fichier pour un document avec Symfony 2

Je vais déjà vous mettre le code :

Entité : Document.php

<?php

namespace AppBundle\Entity;

use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Info
 *
 * @ORM\Table(name="document")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\DocumentRepository")
 * @ORM\HasLifecycleCallbacks()
 * @UniqueEntity(fields="title", message="Un document existe déjà avec ce titre.")
 */
class Document {

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     * @Assert\NotBlank()
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="resume", type="text", nullable=true)
     */
    private $resume;

    /**
     * @var DateTime
     *
     * @ORM\Column(name="date", type="date", nullable=true)
     */
    private $date;

    /**
     * @var String 
     * @ORM\Column(name="path", type="text", length=255, nullable=true)
     */
    private $path;

    /**
     * @Assert\File(maxSize="500k")
     */
    private $file;

    /**
     *
     * @ORM\ManyToOne(targetEntity="DocumentType", inversedBy="documents", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
     *
     * */
    private $documentType;

    /**
     * @ORM\ManyToMany(targetEntity="Author", inversedBy="documents", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
     * */
    private $authors;

    /**
     * @ORM\ManyToMany(targetEntity="Event", inversedBy="documents", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
     * */
    private $events;

    /**
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="documents", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
     */
    private $tags;

    /**
     * @var DateTime2
     *
     * @ORM\Column(name="creation_date", type="datetime", nullable=true)
     * @Assert\DateTime()
     */
    private $creationDate;

    /**
     * @var DateTime2
     *
     * @ORM\Column(name="last_updated", type="datetime", nullable=true)
     * @Assert\DateTime()
     */
    private $lastUpdated;

    /**
     * Get id
     *
     * @return int
     */
    public function getId() {
        return $this->id;
    }

    /**
     * Constructor
     */
    public function __construct() {
        $this->tags = new ArrayCollection();
        $this->authors = new ArrayCollection();
        $this->events = new ArrayCollection();
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return Document
     */
    public function setTitle($title) {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle() {
        return $this->title;
    }

    /**
     * Set resume
     *
     * @param string $resume
     *
     * @return Document
     */
    public function setResume($resume) {
        $this->resume = $resume;

        return $this;
    }

    /**
     * Get resume
     *
     * @return string
     */
    public function getResume() {
        return $this->resume;
    }

    /**
     * Set date
     *
     * @param DateTime $date
     *
     * @return Document
     */
    public function setDate($date) {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return DateTime
     */
    public function getDate() {
        return $this->date;
    }

    /**
     * Add tag
     *
     * @param Tag $tag
     *
     * @return Document
     */
    public function addTag(Tag $tag) {
        if (!$this->tags->contains($tag)) {
            $tag->getDocuments()->add($this);
            $this->tags->add($tag);
        }

        return $this;
    }

    /**
     * Remove tag
     *
     * @param Tag $tag
     */
    public function removeTag(Tag $tag) {
        if ($this->tags->contains($tag)) {
            $tag->getDocuments()->removeElement($this);
            $this->tags->removeElement($tag);
        }

        return $this;
    }

    /**
     * Get tags
     *
     * @return Collection
     */
    public function getTags() {
        return $this->tags;
    }

    /**
     * Set documentType
     *
     * @param DocumentType $documentType
     *
     * @return Document
     */
    public function setDocumentType(DocumentType $documentType = null) {
        $this->documentType = $documentType;

        return $this;
    }

    /**
     * Get documentType
     *
     * @return DocumentType
     */
    public function getDocumentType() {
        return $this->documentType;
    }

    /**
     * Add author
     *
     * @param Author $author
     *
     * @return Document
     */
    public function addAuthor(Author $author) {
        if (!$this->authors->contains($author)) {
            $author->getDocuments()->add($this);
            $this->authors->add($author);
        }

        return $this;
    }

    /**
     * Remove author
     *
     * @param Author $author
     */
    public function removeAuthor(Author $author) {
        if ($this->authors->contains($author)) {
            $author->getDocuments()->removeElement($this);
            $this->authors->removeElement($author);
        }

        return $this;
    }

    /**
     * Get authors
     *
     * @return Collection
     */
    public function getAuthors() {
        return $this->authors;
    }

    /**
     * Add event
     *
     * @param Event $event
     *
     * @return Document
     */
    public function addEvent(Event $event) {
        if (!$this->events->contains($event)) {
            $event->getDocuments()->add($this);
            $this->events->add($event);
        }

        return $this;
    }

    /**
     * Remove event
     *
     * @param Event $event
     */
    public function removeEvent(Event $event) {
        if ($this->events->contains($event)) {
            $event->getDocuments()->removeElement($this);
            $this->events->removeElement($event);
        }

        return $this;
    }

    /**
     * Get events
     *
     * @return Collection
     */
    public function getEvents() {
        return $this->events;
    }

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null) {
        $this->file = $file;
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile() {
        return $this->file;
    }

    public function getAbsolutePath() {
        return null === $this->path ? null : $this->getUploadRootDir() . '/' . $this->path;
    }

    public function getWebPath() {
        return null === $this->path ? null : $this->getUploadDir() . '/' . $this->path;
    }

    protected function getUploadRootDir() {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__ . '/../../../../web/' . $this->getUploadDir();
    }

    protected function getUploadDir() {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'img/uploads/avatars';
    }

    public function upload() {
        // the file property can be empty if the field is not required
        //var_dump($this->getFile());die();
        if (null === $this->getFile()) {
            return;
        }

        // use the original file name here but you shoul    d
        // sanitize it at least to avoid any security issues
        // move takes the target directory and then the
        // target filename to move to
        $this->getFile()->move(
                $this->getUploadRootDir(), $this->getFile()->getClientOriginalName()
        );

        // set the path property to the filename where you've saved the file
        $this->path = $this->getFile()->getClientOriginalName();

        // clean up the file property as you won't need it anymore
        $this->file = null;
    }

    /**
     * Set creationDate
     * @ORM\PrePersist()
     * @return Document
     */
    public function setCreationDate() {
        $this->creationDate = new \DateTime();

        return $this;
    }

    /**
     * Get creationDate
     *
     * @return DateTime
     */
    public function getCreationDate() {
        return $this->creationDate;
    }

    /**
     * Set lastUpdated
     *
     * @ORM\PrePersist()
     * @ORM\PreFlush()
     * 
     * @return Document
     */
    public function setLastUpdated() {
        $this->lastUpdated = new \DateTime();

        return $this;
    }

    /**
     * Get lastUpdated
     *
     * @return DateTime
     */
    public function getLastUpdated() {
        return $this->lastUpdated;
    }

    /**
     * Set path
     *
     * @param string $path
     *
     * @return Document
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Get path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}

Controller : DocumentController.php

/**
     * @Route("/editer/document/{id}", name="editDocument")
     */
    public function editDocumentAction(Request $request, $id) {

        $logger = $this->get('logger');

        $em = $this->getDoctrine()->getManager();
        $document = $em->getRepository('AppBundle:Document')->findOneById($id);
        $form = $this->createForm(new DocumentType(), $document);
        $form->handleRequest($request);

        if ($form->isValid()) {

            $em = $this->getDoctrine()->getManager();

            $document->uploadFileDocument();
            $em->persist($document);
            $em->flush();

            $info = $this->get('translator')->trans('document.alerts.modified', array('%entityName%' => $document->getTitle()));
            $request->getSession()->getFlashBag()->add('info', $info);
            return new JsonResponse(array('message' => 'Success!'), 200);
        } else {

            $response = new JsonResponse(array(
                'form' => $this->renderView('Document/Forms/form-add-document.html.twig', array('form' => $form->createView(),
                    'action' => $this->generateUrl('editDocument', array('id' => $id))))), 400);
            return $response;
        }
    }

DocumentType.php :

<?php

namespace AppBundle\Form;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class DocumentType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class,
                    array('label' => 'document.form.label.title'))
            ->add('resume', TextareaType::class,
                    array('label' => 'document.form.label.resume'))
            ->add('date', DateType::class, array(
                'label' => 'document.form.label.date',
                'widget'      => 'single_text',
                'format'      => 'dd/MM/yyyy'
            ))
            ->add('events', EntityType::class, array(
                'label' => 'document.form.label.events',
                'class'    => 'AppBundle:Event',
                'choice_label' => 'name',
                'multiple' => true,
                'required' => false))
            ->add('authors', EntityType::class, array(
                'label' => 'document.form.label.authors',
                'class'    => 'AppBundle:Author',
                'choice_label' => function ($allChoices) {
                    $pseudos = '';
                    if(!empty($allChoices->getPseudos())) {
                        $pseudos = '(' . $allChoices->getPseudos() . ')';
                    }
                    return $allChoices->getName() . ' ' . $pseudos;
                },
                'multiple' => true))
            ->add('documentType', EntityType::class, array(
                'label' => 'document.form.label.type',
                'class'    => 'AppBundle:DocumentType',
                'choice_label' => 'name',
                'multiple' => false,
                'required'=>false))   
            ->add('tags', EntityType::class, array(
                'label' => 'document.form.label.tags',
                'class'    => 'AppBundle:Tag',
                'choice_label' => 'name',
                'multiple' => true,
                'required'=>false))
            ->add("file", 'file', array(
                'label' => 'document.form.label.file',
                'required' => false))
            ->add('save', SubmitType::class, array('label' => 'Valider'))
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Document'
        ));
    }
}

L'erreur que j'ai est :

Error: Call to a member function move() on null" at C:\wamp64\www\interface\interface\src\AppBundle\Entity\Document.php line 496 {"exception":"[object] (Symfony\Component\Debug\Exception\FatalErrorException(code: 0): Error: Call to a member function move() on null at C:\wamp64\www\interface\interface\src\AppBundle\Entity\Document.php:496)"} []

Je ne comprends pas pourquoi mon champ "file" est null, il est bien de type 'file'.
Merci d'avance pour votre aide !

12 réponses


ribellu2b
Auteur
Réponse acceptée

en effet Nico, j'ai testé sans ajax et ça fonctionne abec l'entité Document !

Salut,

Que te retourne var_dump($this->getFile())??

Bonjour,

déjà remplace dans ton formulaire :

->add("file", 'file', array(
                'label' => 'document.form.label.file',
                'required' => false))

par

->add("file", FileType::class, array(
                'label' => 'document.form.label.file',
                'required' => false))

dans ton controller, tu as :

$document->uploadFileDocument();

que l'on ne trouve pas dans Document

Bonjour Nico,

D'abord, merci d'avoir pris le temps de lire mon sujet.
En ce qui concerne le uploadFileDocument, c'est une erreur de ma part (ancien nom de fonction), actuellement j'utilise bien upload() :

 public function editDocumentAction(Request $request, $id) {

        $logger = $this->get('logger');

        $em = $this->getDoctrine()->getManager();
        $document = $em->getRepository('AppBundle:Document')->findOneById($id);
        $form = $this->get('form.factory')->create(new DocumentType, $document);
        $form->handleRequest($request);

        if ($form->isValid()) {

            if($document->getFile() == null) {
                $logger->info("file is null");
            }
            $em = $this->getDoctrine()->getManager();   
            $document->upload();
            $em->persist($document);
            $em->flush();

            $info = $this->get('translator')->trans('document.alerts.modified', array('%entityName%' => $document->getTitle()));
            $request->getSession()->getFlashBag()->add('info', $info);
            return new JsonResponse(array('message' => 'Success!'), 200);
        } else {

            $response = new JsonResponse(array(
                'form' => $this->renderView('Document/Forms/form-add-document.html.twig', array('form' => $form->createView(),
                    'action' => $this->generateUrl('editDocument', array('id' => $id))))), 400);
            return $response;
        }
    }

Dans le log, j'ai bien le "File is null"

et j'ai testé ton idée d'utiliser FileType::class : :

->add("file", FileType::class, array(
                'label' => 'document.form.label.file',
                'required' => false))

Mais j'ai toujours cette erreur me disant :

[2016-04-13 10:55:04] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalErrorException: "Error: Call to a member function move() on null" at C:\wamp64\www\interface\interface\src\AppBundle\Entity\Document.php line 407 {"exception":"[object] (Symfony\Component\Debug\Exception\FatalErrorException(code: 0): Error: Call to a member function move() on null at C:\wamp64\www\interface\interface\src\AppBundle\Entity\Document.php:407)"} []

Je n'avais pas dans l'idée que FileType::class resolve ton problème, c'est juste pour rendre ton code plus consistant.

juste pour test dans ton controller remplace :

if($document->getFile() == null) {          // car par exemple array() == null est vrai ou une pour chaine vide ""==null est vrai
                $logger->info("file is null");
            }

par

if($document->getFile() === null) { // alors que array() === null est faux ou une pour chaine vide ""===null est faux
                $logger->info("file is null");
            }

pour dire ton getFile() ne retourne pas forcement un null strict => donc passe au travers de :

if (null === $this->getFile()) {
            return;
        }

Je viens de tester avec :

if($document->getFile() === null) {
                $logger->info("File is null");
            }

et le slogs m'affichent toujours : File is null

Je vois vraiment pas ce que j'ai oublié dans le code pour que ça ne fonctionne pas

Tu n'as pas modifié ton entity, c'est bien celle que tu as mis au debut de ton post ?

reparts au propre:
supprimes les caches dev, prod
mets à jour ton schema en db
vide ton dossier image
vide tes tables éventuellement
vérifies que tu n'as pas de listeners liés à Document

mets :

$document = $em->getRepository('AppBundle:Document')->findOneById($id);
if(!$document){  // ça il faut de toute façon le faire !
    // document not found!
    $logger->info("document not found");
    // envoi en erreur
    ...
}
if($document->getFile() !== null) {
    $document->upload();
    $logger->info("File is not null");
}

J'ai mis :

if ($document->getFile() !== null) {
                $document->upload();
                $logger->info("File is not null");
            } else {
                $logger->info("File is null");
            }

les logs : "app.INFO: File is null [] []"
Et tout est ok pour le reste (schema, listeners etc .. )

Tu devrais essayer de regarder ce que te donne le formulaire avec

$form->getData();

ou sinon passe par la super global $_FILES pour vérifier que tu as bien un fichier saisie dans ton formulaire

En fait, j'ai créé une nouvelle entité : File

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * @ORM\Table(name="file")
 * @ORM\Entity
 * @ORM\Entity(repositoryClass="AppBundle\Repository\FileRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class File
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="path", type="string", length=255)
     */
    private $path;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var UploadedFile
     */
    private $file;

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set path
     *
     * @param string $path
     *
     * @return File
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Get path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return File
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

     /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null) {
        $this->file = $file;
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile() {
        return $this->file;
    }

    public function getAbsolutePath() {
        return null === $this->path ? null : $this->getUploadRootDir() . '/' . $this->path;
    }

    public function getWebPath() {
        return null === $this->path ? null : $this->getUploadDir() . '/' . $this->path;
    }

    public function getUploadRootDir() {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__ . '/../../../web/' . $this->getUploadDir();
    }

    protected function getUploadDir() {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'img/uploads';
    }

    public function upload() {

        if (null === $this->getFile()) {
            return;
        }

        // use the original file name here but you shoul    d
        // sanitize it at least to avoid any security issues
        // move takes the target directory and then the
        // target filename to move to
        $this->getFile()->move(
                $this->getUploadRootDir(), $this->getFile()->getClientOriginalName()
        );

        // set the path property to the filename where you've saved the file
        $this->path = $this->getFile()->getClientOriginalName();

        // clean up the file property as you won't need it anymore
        $this->file = null;
    }
}

J'ai créé le formulaire pour cette entité :

<?php

namespace AppBundle\Form;

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

class FileType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, 
                    array('label' => 'Nom', 'required' => false))
            ->add("file", 'file', array(
                'label' => 'document.form.label.file',
                'required' => false))
            ->add('save', SubmitType::class, array('label' => 'Valider')) 
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\File'
        ));
    }
}

et mon controller:

  /**
     * @Route("/files/add", name="addFile")
     */
    public function addFileAction(Request $request) {

        $logger = $this->get('logger');
        $file = new File;
        $form = $this->get('form.factory')->create(new FileType, $file);
        $form->handleRequest($request);
        $logger->info($file->getUploadRootDir());
        if ($form->isValid()) {

            $em = $this->getDoctrine()->getManager();
            $file->upload();
            $em->persist($file);
            $em->flush();

            $request->getSession()->getFlashBag()->add('info', "Fichier ajouté");

            return $this->render('File/page-files.html.twig', array(
                        'form' => $form->createView(),
                        'title' => "Files",
                        'action' => $this->generateUrl('addFile')
            ));
        } else {

            return $this->render('File/page-files.html.twig', array(
                        'form' => $form->createView(),
                        'title' => "Files",
                        'action' => $this->generateUrl('addFile')
            ));
        }
    }

Et ça marche impeccable. Maintenant, faut comprendre pourquoi l'upload intégré directement dans le formulaire de l'entité Document ne fonctionne pas.

Il y a une différence importante entre les deux :
premier cas ( extrait controller ) JsonResponse (tu charges ton fichier en ajax ??)

$response = new JsonResponse(array(
                'form' => $this->renderView('Document/Forms/form-add-document.html.twig', array('form' => $form->createView(),
                    'action' => $this->generateUrl('editDocument', array('id' => $id))))), 400);
            return $response;

deuxième cas ( extrait controller ) render

return $this->render('File/page-files.html.twig', array(
                        'form' => $form->createView(),
                        'title' => "Files",
                        'action' => $this->generateUrl('addFile')
            ));

je suis sûr à 99.99999999% que si dans le premier cas tu fais un affichage normal (comme 2eme cas) tu n'auras pas de problème

 public function editDocumentAction(Request $request, $id) {
        $em = $this->getDoctrine()->getManager();
        $document = $em->getRepository('AppBundle:Document')->findOneById($id);
        if(!$document){
           return $this->createNotFoundException("Document introuvable");
        }
        $form = $this->createForm(DocumentType::class, $document);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();   
             if($document->getFile() == null) {
                $this->get('logger')->info("file is null");
            } else {
                  $document->upload();
             }
            $em->persist($document);
            $em->flush();
            $info = $this->get('translator')->trans('document.alerts.modified', array('%entityName%' => $document->getTitle()));
            $request->getSession()->getFlashBag()->add('info', $info);
        } 

      return $this->render("Document/Forms/form-add-document.html.twig", ["form"=>$form->createView()]);
    }