1
1
How to handle File Uploads with Doctrine
2
2
========================================
3
3
4
- Handling file uploads with Doctrine entities is no much different than
5
- handling any other upload. But if you want to integrate the file upload into
6
- the entity lifecycle (creation, update, and removal), you need to take care of
7
- a lot of details we will talk about in this cookbook entry .
4
+ Handling file uploads with Doctrine entities is no different than handling
5
+ any other file upload. In other words, you're free to move the file in your
6
+ controller after handling a form submission. For examples of how to do this,
7
+ see the :doc: ` file type reference</reference/forms/types/file> ` page .
8
8
9
- First, let's create a simple Doctrine Entity to work with::
9
+ If you choose to, you can also integrate the file upload into your entity
10
+ lifecycle (i.e. creation, update and removal). In this case, as your entity
11
+ is created, updated, and removed from Doctrine, the file uploading and removal
12
+ processing will take place automatically (without needing to do anything in
13
+ your controller);
14
+
15
+ To make this work, you'll need to take care of a number of details, which
16
+ will be covered in this cookbook entry.
17
+
18
+ Basic Setup
19
+ -----------
20
+
21
+ First, create a simple Doctrine Entity class to work with::
22
+
23
+ // src/Acme/DemoBundle/Entity/Download.php
24
+ namespace Acme\DemoBundle\Entity;
10
25
11
26
use Doctrine\ORM\Mapping as ORM;
27
+ use Symfony\Component\Validator\Constraints as Assert;
12
28
13
29
/**
14
30
* @ORM\Entity
15
31
*/
16
- class Document
32
+ class Download
17
33
{
18
34
/**
19
35
* @ORM\Id @ORM\Column(type="integer")
@@ -43,29 +59,41 @@ First, let's create a simple Doctrine Entity to work with::
43
59
}
44
60
}
45
61
46
- A ``Document `` has a name and it is associated with a file. The ``path ``
47
- property stores the relative path to the file and ``getFullPath() `` uses the
48
- ``getUploadRootDir() `` to return the absolute path to the document.
62
+ The ``Download `` entity has a name and it is associated with a file. The ``path ``
63
+ property stores the relative path to the file and is persisted to the database.
64
+ The ``getFullPath() `` is a convenience method that uses the ``getUploadRootDir() ``
65
+ method to return the absolute path to the download file.
49
66
50
67
.. tip ::
51
68
52
- If you have not done so yet , you should probably read the
69
+ If you have not done so already , you should probably read the
53
70
:doc: `file</reference/forms/types/file> ` type documentation first to
54
71
understand how the basic upload process works.
55
72
56
- To receive the uploaded file, we use a "virtual" ``file `` field::
73
+ To handle the actual file upload in the form, use a "virtual" ``file `` field.
74
+ For example, if you're building your form directly in a controller, it might
75
+ look like this::
57
76
58
- $form = $this->createFormBuilder($document)
59
- ->add('name')
60
- ->add('file')
61
- ->getForm()
62
- ;
77
+ public function uploadAction()
78
+ {
79
+ // ...
63
80
64
- Validation rules should be declared on this virtual ``file `` property::
81
+ $form = $this->createFormBuilder($document)
82
+ ->add('name')
83
+ ->add('file')
84
+ ->getForm()
85
+ ;
65
86
66
- use Symfony\Component\Validator\Constraints as Assert;
87
+ // ...
88
+ }
67
89
68
- class Document
90
+ Next, create this property on your ``Download `` class and add some validation
91
+ rules::
92
+
93
+ // src/Acme/DemoBundle/Entity/Download.php
94
+
95
+ // ...
96
+ class Download
69
97
{
70
98
/**
71
99
* @Assert\File(maxSize="6000000")
@@ -77,16 +105,23 @@ Validation rules should be declared on this virtual ``file`` property::
77
105
78
106
.. note ::
79
107
80
- As we are using the ``File `` constraint, Symfony2 will automatically guess
81
- that the field is a file upload input; that's why we have not set it
82
- explicitly during form creation.
108
+ As you are using the ``File `` constraint, Symfony2 will automatically guess
109
+ that the form field is a file upload input. That's why you did not have
110
+ to set it explicitly when creating the form above (``->add('file') ``).
111
+
112
+ The following controller shows you how to handle the entire process::
83
113
84
- The following controller shows you how to manage the form::
114
+ use Acme\DemoBundle\Entity\Download;
115
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
116
+ // ...
85
117
86
- public function uploadAction(Post $post)
118
+ /**
119
+ * @Template()
120
+ */
121
+ public function uploadAction()
87
122
{
88
- $document = new Document ();
89
- $form = $this->createFormBuilder($document )
123
+ $download = new Download ();
124
+ $form = $this->createFormBuilder($download )
90
125
->add('name')
91
126
->add('file')
92
127
->getForm()
@@ -97,14 +132,14 @@ The following controller shows you how to manage the form::
97
132
if ($form->isValid()) {
98
133
$em = $this->getDoctrine()->getEntityManager();
99
134
100
- $em->persist($document );
135
+ $em->persist($download );
101
136
$em->flush();
102
137
103
- $this->redirect('...');
138
+ $this->redirect($this->generateUrl( '...') );
104
139
}
105
140
}
106
141
107
- return array('post' => $post, ' form' => $form->createView());
142
+ return array('form' => $form->createView());
108
143
}
109
144
110
145
.. note ::
@@ -121,12 +156,14 @@ The following controller shows you how to manage the form::
121
156
<input type="submit" value="Upload Document" />
122
157
</form>
123
158
124
- The previous code will automatically persist document entities with their
125
- names, but it will do nothing about the file, because it is not managed by
126
- Doctrine. However, moving the file can be done just before the document is
127
- persisted to the database by calling the ``move() `` method of the
128
- :class: `Symfony\\ Component\\ HttpFoundation\\ File\\ UploadedFile ` instance
129
- returned for the ``file `` field when the form is submitted::
159
+ The previous controller will automatically persist the ``Download `` entity
160
+ with the submitted name, but it will do nothing about the file and the ``path ``
161
+ property will be blank.
162
+
163
+ An easy way to handle the file upload is to move it just before the entity is
164
+ persisted and then set the ``path `` property accordingly. Start by calling
165
+ a new ``upload() `` method on the ``Download `` class, which you'll create
166
+ in a moment to handle the file upload::
130
167
131
168
if ($form->isValid()) {
132
169
$em = $this->getDoctrine()->getEntityManager();
@@ -139,7 +176,8 @@ returned for the ``file`` field when the form is submitted::
139
176
$this->redirect('...');
140
177
}
141
178
142
- And here is the implementation of the ``upload `` method::
179
+ The ``upload() `` method will take advantage of the :class: `Symfony\\ Component\\ HttpFoundation\\ File\\ UploadedFile `
180
+ object, which is what's returned after a ``file `` field is submitted::
143
181
144
182
public function upload()
145
183
{
@@ -149,46 +187,52 @@ And here is the implementation of the ``upload`` method::
149
187
}
150
188
151
189
// we use the original file name here but you should
152
- // sanitize at least it to avoid any security issues
190
+ // sanitize it at least to avoid any security issues
191
+
192
+ // move takes the target directory and then the target filename to move to
153
193
$this->file->move($this->getUploadRootDir(), $this->file->getOriginalName());
154
194
195
+ // set the path property to the filename where you'ved saved the file
155
196
$this->setPath($this->file->getOriginalName());
156
197
157
- // clean up the file property as we won't need it anymore
198
+ // clean up the file property as you won't need it anymore
158
199
unset($this->file);
159
200
}
160
201
202
+ Using Lifecycle Callbacks
203
+ -------------------------
204
+
161
205
Even if this implementation works, it suffers from a major flaw: What if there
162
- is a problem when the entity is persisted? The file is already moved to its
163
- final location but the entity still references the previous file.
206
+ is a problem when the entity is persisted? The file would have already moved
207
+ to its final location even though the entity's ``path `` property didn't
208
+ persist correctly.
164
209
165
- To avoid these issues, we are going to change the implementation so that the
166
- database operation and the moving of the file becomes atomic: if there is a
167
- problem when persisting the entity or if the file cannot be moved, then
168
- nothing happens .
210
+ To avoid these issues, you should change the implementation so that the database
211
+ operation and the moving of the file become atomic: if there is a problem
212
+ persisting the entity or if the file cannot be moved, then * nothing * should
213
+ happen .
169
214
170
- To make the operation atomic, we need to do the moving of the file when
171
- Doctrine persists the entity to the database. This can be accomplished by
172
- hooking into the entity lifecycle ::
215
+ To do this, you need to move the file right as Doctrine persists the entity
216
+ to the database. This can be accomplished by hooking into an entity lifecycle
217
+ callback ::
173
218
174
219
/**
175
220
* @ORM\Entity
176
221
* @ORM\HasLifecycleCallbacks
177
222
*/
178
- class Document
223
+ class Download
179
224
{
180
225
}
181
226
182
- And here is the ``Document `` class that shows the final version with all
183
- lifecycle callbacks implemented::
227
+ Next, refactor the ``Download `` class to take advantage of these callbacks::
184
228
185
229
use Symfony\Component\HttpFoundation\File\UploadedFile;
186
230
187
231
/**
188
232
* @ORM\Entity
189
233
* @ORM\HasLifecycleCallbacks
190
234
*/
191
- class Document
235
+ class Download
192
236
{
193
237
/**
194
238
* @ORM\PrePersist()
@@ -212,7 +256,7 @@ lifecycle callbacks implemented::
212
256
213
257
// you must throw an exception here if the file cannot be moved
214
258
// so that the entity is not persisted to the database
215
- // which the UploadedFile move() method does
259
+ // which the UploadedFile move() method does automatically
216
260
$this->file->move($this->getUploadRootDir(), $this->path);
217
261
218
262
unset($this->file);
@@ -229,9 +273,16 @@ lifecycle callbacks implemented::
229
273
}
230
274
}
231
275
276
+ The class now does everything you need: it generates a unique filename before
277
+ persisting, moves the file after persisting, and removes the file if the
278
+ entity is ever deleted.
279
+
280
+ Using the ``id `` as the filename
281
+ --------------------------------
282
+
232
283
If you want to use the ``id `` as the name of the file, the implementation is
233
- slightly different as we need to save the extension under the ``path ``
234
- property, instead of the path ::
284
+ slightly different as you need to save the extension under the ``path ``
285
+ property, instead of the actual filename ::
235
286
236
287
use Symfony\Component\HttpFoundation\File\UploadedFile;
237
288
@@ -264,6 +315,7 @@ property, instead of the path::
264
315
// so that the entity is not persisted to the database
265
316
// which the UploadedFile move() method does
266
317
$this->file->move($this->getUploadRootDir(), $this->id.'.'.$this->file->guessExtension());
318
+
267
319
unset($this->file);
268
320
}
269
321
0 commit comments