Symfony2:使用Ajax和jQuery上传文件

7

我是一名能够翻译文本的助手。

我有一个Symfony2应用程序,其中包含一个表单和一个文件类型字段。我需要上传学生的照片,因此我查看了这份文档:如何上传文件

这是我的代码:

控制器:

public function createAction(Request $request)
{        
    if ($request->isXmlHttpRequest() && !$request->isMethod('POST')) {
    throw new HttpException('XMLHttpRequests/AJAX calls must be POSTed');
    }

    $entity = new Student();
    $form = $this->createCreateForm($entity);
    $form->handleRequest($request);

    if ($form->isValid()) {
       $file = $entity->getPhoto();

       $fileName = md5(uniqid()).'.'.$file->guessExtension();

       $photoDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/images';

       $file->move($photoDir, $fileName);

       $entity->setPhoto($fileName);

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

       if ($request->isXmlHttpRequest()) {
            return new JsonResponse(array('message' => 'Success!','success' => true), 200);
        }

        if ($request->isMethod('POST')) {
        return new JsonResponse(array('message' => 'Invalid form','success' => false), 400);
    }

      return $this->redirect($this->generateUrl('student_show', array('id' => $entity->getId())));
    }
    return $this->render('BackendBundle:Student:new.html.twig', array(
        'entity' => $entity,
        'form'   => $form->createView(),
    ));
}

实体:

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

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


   public function setPhoto($photo)
   {
    $this->photo = $photo;

    return $this;
   }

   public function getPhoto()
   {
    return $this->photo;
   }

表单类型:

   //...

   ->add('photo', 'file', array('required' => false))

   //...

Javascript:
 //...

$('.form_student').on("submit",function(event) {
 event.preventDefault();

 $.ajax({
  type: 'POST',
  url: Routing.generate('student_create'),
  data: $(this).serialize(),
  dataType: 'json',

  success: function(response) {

   alert(response.message);
  },
  error: function (response, desc, err){
      if (response.responseJSON && response.responseJSON.message) {
         alert(response.responseJSON.message);
      }
      else{
         alert(desc);
      }
  }
 });
});

我现在遇到的问题是,我需要通过Ajax请求来完成这个任务,但是不知道如何发送文件字段,并且在Symfony控制器中使用它。

我看到过一些FormData(),但不知道如何使用它。

你能帮我吗?


你能帮助我帮助你吗?我不想去其他地方查看你的代码。 - Mad Physicist
嗨@MadPhysicist。抱歉,我已经添加了代码。 - Joseph
1
@Joseph 如果我没记错的话,你可以添加一个答案并接受它。 - A.L
3个回答

11

我已经解决了我的代码中的更改:

  • data: new FormData($(this)[0])替代data: $ (this).serialize()

  • 添加Ajax请求:

    processData: false, contentType: false, cache: false,

文件已成功发送。


1
new FormData() HTML5元素破坏了与IE8/9的兼容性 - 序列化应该没有问题。真正解决问题的是{processData: false, contentType: false} - maschmann

3
我用以下方法解决了这个问题,如果你想使用Ajax来完成它。
在你的实体中声明如下:
/** 
 *@ORM\HasLifecycleCallbacks
 */
class Student
{
    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    public $path;

    /**
     * @Assert\File(
     *   maxSize="60000",
     * )
     */
    private $file;

    private $temp;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this->file = $file;
        if (isset($this->path)) {
            // store the old name to delete after the update
            $this->temp = $this->path;
            $this->path = null;
        } else {
            $this->path = 'initial';
        }
    }

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

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

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

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

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

    /**
     * @ORM\PrePersist()
     * @ORM\PreUpdate()
     */
    public function preUpload()
    {
        if (null !== $this->getFile()) {
            // do whatever you want to generate a unique name
            $filename = basename($this->getFile()->getClientOriginalName(),'.'.$this->getFile()->getClientOriginalExtension());
            $this->path = $filename.'.'.$this->getFile()->getClientOriginalExtension();
            if(file_exists($this->getUploadRootDirPath().'/'.$this->path)==1)
            {
                $date = date('-d_M_Y_H:i');
                $this->path = $filename.$date.'.'.$this->getFile()->getClientOriginalExtension();
            }
        }
    }

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this->getFile()) {
            return;
        }

        $this->getFile()->move($this->getUploadRootDirPath(), $this->path);

        if (isset($this->temp)) {
            // delete the old image
            unlink($this->getUploadRootDirPath().'/'.$this->temp);
            // clear the temp image path
            $this->temp = null;
        }
        $this->file = null;
    }


    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        $file = $this->getAbsolutePath();
        if ($file) {
            unlink($file);
        }
    }
}

在你的FormType中:
->add('file', null, array('label' => 'Profile Picture', 'required' => false))

在你的控制器中:
   $entity->setFile($request->files->get('photo')); //here you have get your file field name
   $em->persist($entity);
   $em->flush();

你的 AJAX 看起来还不错,但如果它不能工作,那就使用


  data:new FormData(this),

代替,而不是
  data: $(this).serialize(),

并在ajax中添加这两个参数:
      processData: false,
      contentType: false  

你可以根据需求更改保存文件的方法,并将路径字段更改为照片。

嗨@herr,感谢您的回复。最终,我只需更改我的代码,将data: $(this).serialize()更改为data: new FormData($(this)[0])即可解决问题。 - Joseph
抱歉 @Joseph,我忘记在答案中添加这两个参数 processData: false, contentType: false。在使用ajax发送文件时需要它们。 - herr
嗨@herr,我有一个问题,现在我无法在我的代码中留空文件的值,因为它给了我这个错误:Error: Call to a member function guessExtension() on a non-object in...。我添加了一个条件来测试控制器,但是上传文件的代码块仍然会运行,但仍然会出现该错误,也许我做得不好。我也尝试了你的代码,但我收到了一个验证消息,已经显示了其他代码:This form can not have additional fields。您是否知道如何修复以便可以在表单中留空文件? - Joseph
嗨@herr,最终我通过加入条件if("on"!=$entity->getPhoto())解决了这个问题,因为没有上传文件的代码之前,数据库中的文件字段中会保留“on”值。你知道为什么会保存那个值吗? - Joseph
在你的代码中,你必须测试 if($entity->getPhoto() !='')(空格或null)。在我的代码中,你必须检查 if($request->files->get('photo'))。 - herr
这正是我所想的,@herr。但是使用 ""(空格)或 "null" 条件不满足,如果我使用值 "on",则条件得到满足,但是我不知道因为如果我在表单上将其留空,文件会返回该值。 - Joseph

0

更新:

我认为问题在于,如果您通过ajax提交,则必须在控制器中反序列化接收到的内容。

此外,您应该显式地对表单进行序列化,而不是使用this进行序列化。

var data= $('form.form_student').serialize();

发布错误信息会有助于确定导致错误的原因。 - Nickolaus
返回的错误信息是:“找不到文件”@Nickolaus,我认为这是因为Ajax必须以另一种方式发送文件,我在外面看到过,但不知道。没有Ajax请求,它会正确保存。 - Joseph
嗨@Nickolaus,如果您不选择文件并将Ajax请求留空,则控制器中的反序列化成功,但问题在于我想保存文件时。包含文件序列化字段的值为“on”,因此这一事实非常重要。我该如何反序列化然后在Symfony中使用该数据? - Joseph
上传的文件不可见作为值...要进行反序列化,请使用json_decode函数。 - Nickolaus
抱歉@Nickolaus,但我仍然没有看到它。你能否向我展示如何使用我展示的代码进行反序列化并使用文件分配给变量$ file driver? - Joseph
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接