如何处理文件上传与原则
编辑本页警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 2.2,现已不再维护。
读本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。
如何处理文件上传与原则
使用Doctrine实体处理文件上传与处理任何其他文件上传没有什么不同。换句话说,在处理表单提交之后,您可以自由地在控制器中移动文件。有关如何执行此操作的示例,请参见文件类型引用页面。
如果你选择这样做,你也可以将文件上传集成到你的实体生命周期中(即创建、更新和删除)。在这种情况下,当您的实体从Doctrine中创建、更新和删除时,文件上传和删除处理将自动进行(不需要在控制器中执行任何操作)。
要做到这一点,您需要注意许多细节,这些细节将在本烹饪书条目中介绍。
基本设置
首先,创建一个简单的Doctrine实体类来使用:
12 34 56 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 / / Document.php DemoBundle /实体名称空间Acme\DemoBundle\实体;使用学说\ORM\映射作为ORM;使用ob娱乐下载\组件\验证器\约束作为断言;/ * * *@ORM实体\ * /类文档{/ * * *@ORM\ Id *@ORM\列(type =“整数”)*@ORM\ GeneratedValue(策略=“汽车”)* /公共$id;/ * * *@ORM\列(type="string", length=255) *@Assert\ NotBlank * /公共$的名字;/ * * *@ORM\Column(type="string", length=255, nullable=true) */公共$路径;公共函数getAbsolutePath(){返回零= = =$这->路径?零:$这->getUploadRootDir()。' / '.$这->路径;}公共函数getWebPath(){返回零= = =$这->路径?零:$这->getUploadDir()。' / '.$这->路径;}受保护的函数getUploadRootDir(){//上传的绝对目录路径//文件应该保存返回__DIR__.'/../../../../ web / '.$这->getUploadDir ();}受保护的函数getUploadDir(){//去掉__DIR__,这样就不会搞砸了//当在视图中显示上传的doc/image时。返回'上传/文件;}}
的文档
实体有一个名称,它与一个文件相关联。的路径
属性存储文件的相对路径,并持久化到数据库中。的getAbsolutePath ()
是返回文件的绝对路径的方便方法,而getWebPath ()
是一个返回web路径的方便方法,可以在模板中使用该路径链接到上传的文件。
提示
如果你还没有这样做,你可能应该阅读文件首先输入文欧宝官网下载app档以了解基本的上传过程是如何工作的。
请注意
如果使用注释来指定验证规则(如本例所示),请确保已启用了通过注释进行验证(请参见验证配置).
要处理表单中的实际文件上传,请使用“virtual”文件
字段。例如,如果你直接在控制器中构建表单,它可能是这样的:
1 2 3 4 5 6 7 8 9 10 11
公共函数uploadAction(){/ /……$形式=$这->createFormBuilder ($文档)->add (“名字”)->add (“文件”)->getForm ();/ /……}
接下来,在您的文档
类,并添加一些验证规则:
12 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
使用ob娱乐下载\组件\HttpFoundation\文件\UploadedFile;/ /……类文档{/ * * *@Assert\文件(最大容量= " 6000000 ")* /私人$文件;/** *设置文件* *@paramUploadedFile $file */公共函数setFile(UploadedFile$文件= null){$这->文件=$文件;}/** *获取文件。* *@returnUploadedFile * /公共函数getFile(){返回$这->文件;}}
- YAML
- 注释
- XML
- PHP
1 2 3 4 5 6
# src / Acme / DemoBundle /资源/ config / validation.ymlAcme \ DemoBundle \实体\文档:属性:文件:-文件:最大尺寸:6000000
12 3 4 5 6 7 8 9 10 11 12 13 14 15
/ / src / Acme / / Document.php DemoBundle /实体名称空间Acme\DemoBundle\实体;/ /……使用ob娱乐下载\组件\验证器\约束作为断言;类文档{/ * * *@Assert\文件(最大容量= " 6000000 ")* /私人$文件;/ /……}
1 2 3 4 5 6 7 8
< !--src/Acme/DemoBundle/Resources/config/validation.yml --><类的名字=“Acme \ DemoBundle \实体\文件”><财产的名字=“文件”><约束的名字=“文件”><选项的名字=“最大容量”>6000000选项>约束>财产>类>
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/ / src / Acme / / Document.php DemoBundle /实体名称空间Acme\DemoBundle\实体;/ /……使用ob娱乐下载\组件\验证器\映射\ClassMetadata;使用ob娱乐下载\组件\验证器\约束作为断言;类文档{/ /……公共静态函数loadValidatorMetadata(ClassMetadata$元数据){$元数据->addPropertyConstraint (“文件”,新维护\文件(数组(最大容量的= >6000000)));}}
请注意
当你使用文件
约束时,Symfony2ob娱乐下载将自动猜测表单字段是文件上传输入。这就是为什么在创建上面的表单时不必显式地设置它的原因(- >添加(“文件”)
).
下面的控制器告诉你如何处理整个过程:
12 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
/ /……使用Acme\DemoBundle\实体\文档;使用Sensio赞助\包\FrameworkExtraBundle\配置\模板;/ /……/ * * *@Template() * /公共函数uploadAction(){$文档=新文档();$形式=$这->createFormBuilder ($文档)->add (“名字”)->add (“文件”)->getForm ();如果($这->getRequest ()->isMethod (“职位”)) {$形式->bind ($这->getRequest ());如果($形式->isValid ()) {$新兴市场=$这->getDoctrine ()->getManager ();$新兴市场->persist ($文档);$新兴市场->冲洗();返回$这->重定向($这->generateUrl(…));}}返回数组(“形式”= >$形式->createView ());}
请注意
在编写模板时,不要忘记设置enctype
属性:
- 嫩枝
- PHP
1 2 3 4 5 6 7
<h1>上传文件h1><形式行动=“#”方法=“职位”{{form_enctype(form)}}>{{form_widget(form)}}<输入类型=“提交”价值=“上传文件”/>形式>
1 2 3 4 5 6 7
<h1>上传文件h1><形式行动=“#”方法=“职位”<?php回声$视图['形式']->enctype(形式)美元? > ><?php回声$视图[“形式”]->小部件($形式)? ><输入类型=“提交”价值=“上传文件”/>形式>
前一个控制器将自动持久化文档
实体,但它不会对文件和路径
属性将为空白。
处理文件上传的一种简单方法是在持久化实体之前移动它,然后设置路径
相应的属性。首先调用new上传()
方法。文档
类,您将在稍后创建它来处理文件上传:
1 2 3 4 5 6 7 8 9 10
如果($形式->isValid ()) {$新兴市场=$这->getDoctrine ()->getManager ();$文档->上传();$新兴市场->persist ($文档);$新兴市场->冲洗();返回$这->重定向(…);}
的上传()
方法会占便宜UploadedFile对象,它是在文件
提交字段:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
公共函数上传(){//如果不需要该字段,文件属性可以为空如果(零= = =$这->getFile ()) {返回;}//在这里使用原始文件名,但您应该//至少对其进行消毒以避免任何安全问题// move获取目标目录,然后//要移动到的目标文件名$这->getFile ()->移动($这->getUploadRootDir (),$这->getFile ()->getClientOriginalName ());//设置路径属性为你保存文件的文件名$这->路径=$这->getFile ()->getClientOriginalName ();//清理文件属性,因为你不再需要它了$这->文件=零;}
使用生命周期回调
即使这个实现可以工作,它也有一个重大缺陷:如果在持久化实体时出现问题怎么办?该文件将已经移动到它的最终位置,即使实体的路径
属性没有正确地持久化。
为了避免这些问题,您应该更改实现,以便数据库操作和文件的移动成为原子的:如果存在实体持久化问题或文件无法移动,那么<新兴市场>没有什么新兴市场>应该发生的事情。
为此,您需要在Doctrine将实体持久化到数据库时移动文件。这可以通过钩子到一个实体生命周期回调来实现:
1 2 3 4 5 6 7
/ * * *@ORM* \实体@ORM\ HasLifecycleCallbacks * /类文档{}
接下来,重构文档
类来利用这些回调:
12 34 56 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
使用ob娱乐下载\组件\HttpFoundation\文件\UploadedFile;/ * * *@ORM* \实体@ORM\ HasLifecycleCallbacks * /类文档{私人$临时;/** *设置文件* *@paramUploadedFile $file */公共函数setFile(UploadedFile$文件= null){$这->文件=$文件;//检查我们是否有一个旧的图像路径如果(收取($这->路径)){//保存更新后要删除的旧名称$这->temp =$这->路径;$这->路径=零;}其他的{$这->路径=“初始”;}}/ * * *@ORM* \ PrePersist ()@ORM\ PreUpdate () * /公共函数preUpload(){如果(零= = !$这->getFile ()) {//做任何你想要生成唯一名称的事情$文件名函数= sha1 (mt_rand (),真正的));$这->路径=$文件名.“。”.$这->getFile ()->guessExtension ();}}/ * * *@ORM* \ PostPersist ()@ORM\ PostUpdate () * /公共函数上传(){如果(零= = =$这->getFile ()) {返回;}//如果在移动文件时出现错误,则会出现异常//由move()自动抛出。这将有效地防止//实体从被持久化到数据库错误$这->getFile ()->移动($这->getUploadRootDir (),$这->路径);//检查我们是否有旧的图像如果(收取($这->临时)){//删除旧映像分离($这->getUploadRootDir()。' / '.$这->临时);//清除临时映像路径$这->temp =零;}$这->文件=零;}/ * * *@ORM\ PostRemove () * /公共函数removeUpload(){如果($文件=$这->getAbsolutePath()) {unlink($文件);}}}
这个类现在可以完成您需要的所有事情:它在持久化之前生成一个惟一的文件名,在持久化之后移动文件,并且在删除实体时删除文件。
现在文件的移动由实体原子地处理,调用文件- >上传()
应从控制器上移除:
1 2 3 4 5 6 7 8
如果($形式->isValid ()) {$新兴市场=$这->getDoctrine ()->getManager ();$新兴市场->persist ($文档);$新兴市场->冲洗();返回$这->重定向(…);}
请注意
的@ORM \ PrePersist ()
而且@ORM \ PostPersist ()
事件回调在实体持久化到数据库之前和之后触发。另一方面,@ORM \ PreUpdate ()
而且@ORM \ PostUpdate ()
当实体更新时,将调用事件回调。
谨慎
的PreUpdate
而且PostUpdate
只有在持久化实体的某个字段发生更改时才会触发回调。这意味着,默认情况下,如果只修改美元的文件
属性,这些事件将不会被触发,因为属性本身不是通过Doctrine直接持久化的。一种解决方案是使用更新
字段,并在更改文件时手动修改它。
使用id
作为文件名
如果你想用id
作为文件的名称,实现略有不同,因为您需要将扩展保存在路径
属性,而不是实际的文件名:
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
使用ob娱乐下载\组件\HttpFoundation\文件\UploadedFile;/ * * *@ORM* \实体@ORM\ HasLifecycleCallbacks * /类文档{私人$临时;/** *设置文件* *@paramUploadedFile $file */公共函数setFile(UploadedFile$文件= null){$这->文件=$文件;//检查我们是否有一个旧的图像路径如果(is_file ($这->getAbsolutePath ())) {//保存更新后要删除的旧名称$这->temp =$这->getAbsolutePath ();}其他的{$这->路径=“初始”;}}/ * * *@ORM* \ PrePersist ()@ORM\ PreUpdate () * /公共函数preUpload(){如果(零= = !$这->getFile ()) {$这->路径=$这->getFile ()->guessExtension ();}}/ * * *@ORM* \ PostPersist ()@ORM\ PostUpdate () * /公共函数上传(){如果(零= = =$这->getFile ()) {返回;}//检查我们是否有旧的图像如果(收取($这->临时)){//删除旧映像分离($这->临时);//清除临时映像路径$这->temp =零;}//如果文件不能移动,则必须抛出异常//这样实体就不会持久化到数据库中UploadedFile move()方法所做的$这->getFile ()->移动($这->getUploadRootDir (),$这->id。“。”.$这->getFile ()->guessExtension ());$这->setFile (零);}/ * * *@ORM\ PreRemove () * /公共函数storeFilenameForRemove(){$这->temp =$这->getAbsolutePath ();}/ * * *@ORM\ PostRemove () * /公共函数removeUpload(){如果(收取($这->Temp)){断开链接($这->临时);}}公共函数getAbsolutePath(){返回零= = =$这->路径?零:$这->getUploadRootDir()。' / '.$这->id。“。”.$这->路径;}}
您会注意到,在这种情况下,您需要做更多的工作才能删除文件。在删除它之前,必须存储文件路径(因为它取决于id)。然后,一旦对象完全从数据库中删除,您就可以安全地删除文件(在PostRemove
).