Skip to content

Commit 20ae21b

Browse files
committed
Added a new cookbook about file uploading
1 parent 5c0f8fb commit 20ae21b

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

cookbook/controller/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Controller
66

77
error_pages
88
service
9+
upload_file

cookbook/controller/upload_file.rst

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
.. index::
2+
single: Controller; Upload; File
3+
4+
How to Upload Files
5+
===================
6+
7+
.. note::
8+
9+
Instead of handling file uploading yourself, you may consider using the
10+
`VichUploaderBundle`_ community bundle. This bundle provides all the common
11+
operations (such as file renaming, saving and deleting) and it's tightly
12+
integrated with Doctrine ORM, MongoDB ODM, PHPCR ODM and Propel.
13+
14+
Imagine that you have a ``Product`` entity in your application and you want to
15+
add a PDF brochure for each product. To do so, add a new property called ``brochure``
16+
in the ``Product`` entity::
17+
18+
// src/AppBundle/Entity/Product.php
19+
namespace AppBundle\Entity;
20+
21+
use Doctrine\ORM\Mapping as ORM;
22+
use Symfony\Component\Validator\Constraints as Assert;
23+
24+
class Product
25+
{
26+
// ...
27+
28+
/**
29+
* @ORM\Column(type="string")
30+
*
31+
* @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
32+
* @Assert\File(mimeTypes={ "application/pdf" })
33+
*/
34+
private $brochure;
35+
36+
public function getBrochure()
37+
{
38+
return $this->brochure;
39+
}
40+
41+
public function setBrochure($brochure)
42+
{
43+
$this->brochure = $brochure;
44+
45+
return $this;
46+
}
47+
}
48+
49+
Note that the type of the ``brochure`` column is ``string`` instead of ``binary``
50+
or ``blob`` because it just stores the PDF file name instead of the file contents.
51+
52+
Then, add a new ``brochure`` field to the form that manages ``Product`` entities::
53+
54+
// src/AppBundle/Form/ProductType.php
55+
namespace AppBundle\Form;
56+
57+
use Symfony\Component\Form\AbstractType;
58+
use Symfony\Component\Form\FormBuilderInterface;
59+
60+
class ProductType extends AbstractType
61+
{
62+
public function buildForm(FormBuilderInterface $builder, array $options)
63+
{
64+
$builder
65+
// ...
66+
->add('brochure', 'file', array('label' => 'Brochure (PDF file)'))
67+
// ...
68+
;
69+
}
70+
71+
// ...
72+
}
73+
74+
Now, update the template that renders the form to display the new ``brochure``
75+
field (the exact template code to add depends on the method used by your application
76+
to :doc:`customize form rendering </cookbook/form/form_customization>`):
77+
78+
.. code-block:: html+jinja
79+
80+
{# app/Resources/views/product/new.html.twig #}
81+
<h1>Adding a new product</h1>
82+
83+
{{ form_start() }}
84+
{# ... #}
85+
86+
{{ form_row(form.brochure) }}
87+
{{ form_end() }}
88+
89+
Finally, you need to update the code of the controller that handles the form::
90+
91+
// src/AppBundle/Controller/ProductController.php
92+
namespace AppBundle\ProductController;
93+
94+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
95+
use Symfony\Component\HttpFoundation\Request;
96+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
97+
use AppBundle\Entity\Product;
98+
99+
class ProductController extends Controller
100+
{
101+
/**
102+
* @Route("/product/new", name="app_product_new")
103+
*/
104+
public function newAction(Request $request)
105+
{
106+
//...
107+
108+
if ($form->isValid()) {
109+
// $file stores the uploaded PDF file
110+
$file = $product->getBrochure()
111+
112+
// Generate a unique name for the file before saving it
113+
$fileName = md5(uniqid()).'.'.$file->guessExtension();
114+
115+
// Move the file to the directory where brochures are stored
116+
$brochuresDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/brochures';
117+
$file->move($brochuresDir, $fileName);
118+
119+
// Update the 'brochure' property to store the PDF file name instead of its contents
120+
$product->setBrochure($filename);
121+
122+
// ...
123+
124+
return $this->redirect($this->generateUrl('app_product_list'));
125+
}
126+
127+
return $this->render('product/new.html.twig', array(
128+
'form' => $form->createView()
129+
));
130+
}
131+
}
132+
133+
There are some important things to consider in the code of the above controller:
134+
135+
#. When the form is uploaded, the ``brochure`` property contains the whole PDF
136+
file contents. Since this property stores just the file name, you must set
137+
its new value before persisting the changes of the entity.
138+
#. In Symfony applications, uploaded files are objects of the
139+
:class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class, which
140+
provides methods for the most common operations when dealing with uploaded files.
141+
#. A well-known security best practice is to never trust the input provided by
142+
users. This also applies to the files uploaded by your visitors. The ``Uploaded``
143+
class provides methods to get the original file extension (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getExtension()`),
144+
the original file size (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getSize()`)
145+
and the original file name (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientOriginalName()`).
146+
However, they are considered *not safe* because a malicious user could tamper
147+
that information. That's why it's always better to generate a unique name and
148+
use the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension()`
149+
method to let Symfony guess the right extension according to the file MIME type.
150+
#. The ``UploadedFile`` class also provides a :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::move()`
151+
method to store the file in its intended directory. Defining this directory
152+
path as an application configuration option is considered a good practice that
153+
simplifies the code: ``$this->container->getParameter('brochures_dir')``.
154+
155+
You can now use the following code to link to the PDF brochure of an product:
156+
157+
.. code-block:: html+jinja
158+
159+
<a href="{{ asset('uploads/brochures' ~ product.brochure) }}">View brochure (PDF)</a>
160+
161+
.. _`VichUploaderBundle`: https://github.com/dustin10/VichUploaderBundle

cookbook/map.rst.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
* :doc:`/cookbook/controller/error_pages`
5252
* :doc:`/cookbook/controller/service`
53+
* :doc:`/cookbook/controller/upload_file`
5354

5455
* **Debugging**
5556

0 commit comments

Comments
 (0)