Doctrine是基于數據庫抽像層上的ORM,它可以通過PHP對象輕松訪問所有的數據庫,例如MYSQL,它支持的PHP最低版本為5.2.3,下面我們一起來看看Doctrine文件上傳處理例子,希望文章對各位有幫助.
基本設置,創建一個簡單的Doctrine實體類:
- // 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,他可能是這樣的.
- public function uploadAction()
- {
- // ...
- $form = $this->createFormBuilder($document)
- ->add('name')
- ->add('file')
- ->getForm();
- // ...
- }
下一步,創建file這個屬性到你的Document類中并且添加一些驗證規則:
- 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
- Annotations
- // 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’))時,為什么沒有在表單明確設置為文件上傳的原因.
下面的控制器,告訴您如何處理全部過程:
- // ...
- 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賦值.
- if ($form->isValid()) {
- $em = $this->getDoctrine()->getManager();
- $document->upload();
- $em->persist($document);
- $em->flush();
- return $this->redirect(...);
- }
這個upload()方法利用UploadedFile對象,是它提交后返回file字段:
- 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有持久化問題或者文件不能夠移動,什么事情也沒有發生,為了避免這些問題,你應該改變這種實現方式以便數據庫操作和自動刪除文件:
- /**
- * @ORM/Entity
- * @ORM/HasLifecycleCallbacks
- */
- class Document
- {
- }
接下來,利用這些回調函數重構Document類:
- 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中移除了:
- 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屬性為文件擴展名,而不是實際的文件名:
- 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()
- { //Vevb.com
- if (isset($this->temp)) {
- unlink($this->temp);
- }
- }
- public function getAbsolutePath()
- {
- return null === $this->path
- ? null
- : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path;
- }
- }
你會注意到,在這種情況下,你需要做一點工作,以刪除該文件,在數據刪除之前,你必須保存文件路徑(因為它依賴于ID),然后,一旦對象已經完全從數據庫中刪除,你就可以安全的刪除文件(在數據刪除之后).
新聞熱點
疑難解答