以Doctrine形式上传文件和一些其他方式上传图片是没有什么不同的。换句话说,你可以在表单提交后,自由的处理文件。对于如何做到这一点,请参阅 http://symfony.com/doc/current/reference/forms/types/file.html
你可以将上传文件的处理集成到Entity的生命周期中(如创建,修改和删除时调用)。
在这种情况下的好处,在Doctrine中创建、修改和删除数据时,上传文件和删除文件也会自动完成(必须在控制器中做任何操作)
基本设置
创建一个简单的Doctrine实体类:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | // src/Acme/DemoBundle/Entity/Document.php namespace Acme\DemoBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /**  * @ORM\Entity  */ class Document {     /**      * @ORM\Id      * @ORM\Column(type="integer")      * @ORM\GeneratedValue(strategy="AUTO")      */     public $id;     /**      * @ORM\Column(type="string", length=255)      * @Assert\NotBlank      */     public $name;     /**      * @ORM\Column(type="string", length=255, nullable=true)      */     public $path;     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 'uploads/documents';     } } | 
该document实体有一个名称与文件相关联。这个path属性存储一个文件的相对路径并且在数据库中存储。这个getAbsolutePath()会返回一个绝对路径,getWebPath()会返回一个web路径,用于模板加入上传文件链接。
如果你还没有这样做的话,你应该阅读http://symfony.com/doc/current/reference/forms/types/file.html首先了解基本的上传过程。
如果您使用注释来验证规则(如本例所示),请确保你启用了注释验证(见http://symfony.com/doc/current/book/validation.html#book-validation-configuration)。
在处理一个实际的文件上传时,使用一个“虚拟”的file字段。例如,如果你在controller中直接构建一个form,他可能是这样的:
| 1 2 3 4 5 6 7 8 9 10 11 | public function uploadAction() {     // ...     $form = $this->createFormBuilder($document)         ->add('name')         ->add('file')         ->getForm();     // ... } | 
下一步,创建file这个属性到你的Document类中并且添加一些验证规则:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | use Symfony\Component\HttpFoundation\File\UploadedFile; // ... class Document {     /**      * @Assert\File(maxSize="6000000")      */     private $file;     /**      * Sets file.      *      * @param UploadedFile $file      */     public function setFile(UploadedFile $file = null)     {         $this->file = $file;     }     /**      * Get file.      *      * @return UploadedFile      */     public function getFile()     {         return $this->file;     } } | 
annotations
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // src/Acme/DemoBundle/Entity/Document.php namespace Acme\DemoBundle\Entity; // ... use Symfony\Component\Validator\Constraints as Assert; class Document {     /**      * @Assert\File(maxSize="6000000")      */     private $file;     // ... } | 
当你使用File约束,symfony会自动猜测表单字段输入的是一个文件上传。这就是当你创建表单(->add(‘file’))时,为什么没有在表单明确设置为文件上传的原因。
下面的控制器,告诉您如何处理全部过程:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // ... use Acme\DemoBundle\Entity\Document; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Component\HttpFoundation\Request; // ... /**  * @Template()  */ public function uploadAction(Request $request) {     $document = new Document();     $form = $this->createFormBuilder($document)         ->add('name')         ->add('file')         ->getForm();     $form->handleRequest($request);     if ($form->isValid()) {         $em = $this->getDoctrine()->getManager();         $em->persist($document);         $em->flush();         return $this->redirect($this->generateUrl(...));     }     return array('form' => $form->createView()); } | 
以前的controller当提交name自动的存储Document实体,但是他不会做任何关于文件的事情并且path属性也将是空白。
处理文件上传一个简单的方法就是在entity持久化之前设置相应的path属性。在某一时刻处理文件上传时,要调用Document实体类一个upload()方法给path赋值。
| 1 2 3 4 5 6 7 8 9 10 | if ($form->isValid()) {     $em = $this->getDoctrine()->getManager();     $document->upload();     $em->persist($document);     $em->flush();     return $this->redirect(...); } | 
这个upload()方法利用UploadedFile对象,是它提交后返回file字段:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public function upload() {     // the file property can be empty if the field is not required     // 该file属性为空这个属性就不需要了     if (null === $this->getFile()) {         return;     }     // use the original file name here but you should     // 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     // 设置path属性为你保存文件的文件名     $this->path = $this->getFile()->getClientOriginalName();     // clean up the file property as you won't need it anymore     // 清理你不需要的file属性     $this->file = null; } | 
使用生命周期回调
生命周期回调是一种有限的技术,他有一些缺点。如果你想移除Document::getUploadRootDir()方法里的写死的编码__DIR__,最好的方法是开始使用Doctrine listeners。在哪里你将能够注入内核参数,如kernel.root_dir来建立绝对路径。
这种原理工作,他有一个缺陷:也就是说当entity持久化时会有什么问题呢?答:该文件已经转移到了它的最终位置,实体类下的path属性不能够正确的实体化。
(如果entity有持久化问题或者文件不能够移动,什么事情也没有发生)为了避免这些问题,你应该改变这种实现方式以便数据库操作和自动删除文件:
| 1 2 3 4 5 6 7 | /**  * @ORM\Entity  * @ORM\HasLifecycleCallbacks  */ class Document { } | 
接下来,利用这些回调函数重构Document类:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | use Symfony\Component\HttpFoundation\File\UploadedFile; /**  * @ORM\Entity  * @ORM\HasLifecycleCallbacks  */ class Document {     private $temp;     /**      * Sets file.      *      * @param UploadedFile $file      */     public function setFile(UploadedFile $file = null)     {         $this->file = $file;         // check if we have an old image path         // 检查如果我们有一个旧的图片路径         if (isset($this->path)) {             // store the old name to delete after the update             $this->temp = $this->path;             $this->path = null;         } else {             $this->path = 'initial';         }     }     /**      * @ORM\PrePersist()      * @ORM\PreUpdate()      */     public function preUpload()     {         if (null !== $this->getFile()) {             // do whatever you want to generate a unique name             // 去生成一个唯一的名称             $filename = sha1(uniqid(mt_rand(), true));             $this->path = $filename.'.'.$this->getFile()->guessExtension();         }     }     /**      * @ORM\PostPersist()      * @ORM\PostUpdate()      */     public function upload()     {         if (null === $this->getFile()) {             return;         }         // if there is an error when moving the file, an exception will         // be automatically thrown by move(). This will properly prevent         // the entity from being persisted to the database on error         //当移动文件发生错误,一个异常move()会自动抛出异常。         //这将阻止实体持久化数据库发生错误。         $this->getFile()->move($this->getUploadRootDir(), $this->path);         // check if we have an old image         if (isset($this->temp)) {             // delete the old image             unlink($this->getUploadRootDir().'/'.$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);         }     } } | 
如果更改你的entity是由Doctrine event listener 或event subscriber处理,这个
preUpdate()回调函数必须通知Doctrine关于正在做的改变。有关preUpdate事件限制的完整参考请查看 http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate
现在这个类做了你需要的一切:他会在entity持久化之前生成一个唯一的文件名,持久化之后,移动文件,删除文件。
现在移动文件是entity自动完成,这个$document->upload()就应该从controller中移除了:
| 1 2 3 4 5 6 7 8 | if ($form->isValid()) {     $em = $this->getDoctrine()->getManager();     $em->persist($document);     $em->flush();     return $this->redirect(...); } | 
这个@ORM\PrePersist()和@ORM\PostPersist()事件回调:一个是在entity持久化到数据库之前触发,一个是在entity持久化到数据库之后触发。另一方面,
@ORM\PreUpdate()和@ORM\PostUpdate()事件回调时当实体更新时触发。当改变entity字段后进行持久化操作时,
PreUpdate和PostUpdate回调才会被触发。这意味着,默认情况下,你只改变了$file属性,这些事件不会被触发,因为这个属性它自己不会持久化到Doctrine。有一个解决方法,就是创建一个updated字段把它持久化到Doctrine,并当文件改变时手动调整它。
使用ID作为文件名
如果要使用ID作为文件名,实现略有不同,您需要保存path属性为文件扩展名,而不是实际的文件名:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | use Symfony\Component\HttpFoundation\File\UploadedFile; /**  * @ORM\Entity  * @ORM\HasLifecycleCallbacks  */ class Document {     private $temp;     /**      * Sets file.      *      * @param UploadedFile $file      */     public function setFile(UploadedFile $file = null)     {         $this->file = $file;         // check if we have an old image path         if (is_file($this->getAbsolutePath())) {             // store the old name to delete after the update             $this->temp = $this->getAbsolutePath();         } else {             $this->path = 'initial';         }     }     /**      * @ORM\PrePersist()      * @ORM\PreUpdate()      */     public function preUpload()     {         if (null !== $this->getFile()) {             $this->path = $this->getFile()->guessExtension();         }     }     /**      * @ORM\PostPersist()      * @ORM\PostUpdate()      */     public function upload()     {         if (null === $this->getFile()) {             return;         }         // check if we have an old image         if (isset($this->temp)) {             // delete the old image             unlink($this->temp);             // clear the temp image path             $this->temp = null;         }         // you must throw an exception here if the file cannot be moved         // so that the entity is not persisted to the database         // which the UploadedFile move() method does         $this->getFile()->move(             $this->getUploadRootDir(),             $this->id.'.'.$this->getFile()->guessExtension()         );         $this->setFile(null);     }     /**      * @ORM\PreRemove()      */     public function storeFilenameForRemove()     {         $this->temp = $this->getAbsolutePath();     }     /**      * @ORM\PostRemove()      */     public function removeUpload()     {         if (isset($this->temp)) {             unlink($this->temp);         }     }     public function getAbsolutePath()     {         return null === $this->path             ? null             : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path;     } } | 
你会注意到,在这种情况下,你需要做一点工作,以删除该文件。在数据删除之前,你必须保存文件路径(因为它依赖于ID)。然后,一旦对象已经完全从数据库中删除,你就可以安全的删除文件(在数据删除之后)。
