diff --git a/cookbook/controller/index.rst b/cookbook/controller/index.rst
index fc4041abf25..9ee8ca56a17 100644
--- a/cookbook/controller/index.rst
+++ b/cookbook/controller/index.rst
@@ -6,3 +6,4 @@ Controller
error_pages
service
+ upload_file
diff --git a/cookbook/controller/upload_file.rst b/cookbook/controller/upload_file.rst
new file mode 100644
index 00000000000..d374e1473f0
--- /dev/null
+++ b/cookbook/controller/upload_file.rst
@@ -0,0 +1,177 @@
+.. index::
+ single: Controller; Upload; File
+
+How to Upload Files
+===================
+
+.. note::
+
+ Instead of handling file uploading yourself, you may consider using the
+ `VichUploaderBundle`_ community bundle. This bundle provides all the common
+ operations (such as file renaming, saving and deleting) and it's tightly
+ integrated with Doctrine ORM, MongoDB ODM, PHPCR ODM and Propel.
+
+Imagine that you have a ``Product`` entity in your application and you want to
+add a PDF brochure for each product. To do so, add a new property called ``brochure``
+in the ``Product`` entity::
+
+ // src/AppBundle/Entity/Product.php
+ namespace AppBundle\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Component\Validator\Constraints as Assert;
+
+ class Product
+ {
+ // ...
+
+ /**
+ * @ORM\Column(type="string")
+ *
+ * @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
+ * @Assert\File(mimeTypes={ "application/pdf" })
+ */
+ private $brochure;
+
+ public function getBrochure()
+ {
+ return $this->brochure;
+ }
+
+ public function setBrochure($brochure)
+ {
+ $this->brochure = $brochure;
+
+ return $this;
+ }
+ }
+
+Note that the type of the ``brochure`` column is ``string`` instead of ``binary``
+or ``blob`` because it just stores the PDF file name instead of the file contents.
+
+Then, add a new ``brochure`` field to the form that manage the ``Product`` entity::
+
+ // src/AppBundle/Form/ProductType.php
+ namespace AppBundle\Form;
+
+ use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\FormBuilderInterface;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+
+ class ProductType extends AbstractType
+ {
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ // ...
+ ->add('brochure', 'file', array('label' => 'Brochure (PDF file)'))
+ // ...
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'AppBundle\Entity\Product',
+ ));
+ }
+
+ public function getName()
+ {
+ return 'product';
+ }
+ }
+
+Now, update the template that renders the form to display the new ``brochure``
+field (the exact template code to add depends on the method used by your application
+to :doc:`customize form rendering `):
+
+.. code-block:: html+jinja
+
+ {# app/Resources/views/product/new.html.twig #}
+
Adding a new product
+
+ {{ form_start() }}
+ {# ... #}
+
+ {{ form_row(form.brochure) }}
+ {{ form_end() }}
+
+Finally, you need to update the code of the controller that handles the form::
+
+ // src/AppBundle/Controller/ProductController.php
+ namespace AppBundle\ProductController;
+
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Component\HttpFoundation\Request;
+ use AppBundle\Entity\Product;
+ use AppBundle\Form\ProductType;
+
+ class ProductController extends Controller
+ {
+ /**
+ * @Route("/product/new", name="app_product_new")
+ */
+ public function newAction(Request $request)
+ {
+ $product = new Product();
+ $form = $this->createForm(new ProductType(), $product);
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ // $file stores the uploaded PDF file
+ /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
+ $file = $product->getBrochure()
+
+ // Generate a unique name for the file before saving it
+ $fileName = md5(uniqid()).'.'.$file->guessExtension();
+
+ // Move the file to the directory where brochures are stored
+ $brochuresDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/brochures';
+ $file->move($brochuresDir, $fileName);
+
+ // Update the 'brochure' property to store the PDF file name
+ // instead of its contents
+ $product->setBrochure($filename);
+
+ // persist the $product variable or any other work...
+
+ return $this->redirect($this->generateUrl('app_product_list'));
+ }
+
+ return $this->render('product/new.html.twig', array(
+ 'form' => $form->createView()
+ ));
+ }
+ }
+
+There are some important things to consider in the code of the above controller:
+
+#. When the form is uploaded, the ``brochure`` property contains the whole PDF
+ file contents. Since this property stores just the file name, you must set
+ its new value before persisting the changes of the entity.
+#. In Symfony applications, uploaded files are objects of the
+ :class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class, which
+ provides methods for the most common operations when dealing with uploaded files.
+#. A well-known security best practice is to never trust the input provided by
+ users. This also applies to the files uploaded by your visitors. The ``Uploaded``
+ class provides methods to get the original file extension (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getExtension()`),
+ the original file size (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getSize()`)
+ and the original file name (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientOriginalName()`).
+ However, they are considered *not safe* because a malicious user could tamper
+ that information. That's why it's always better to generate a unique name and
+ use the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension()`
+ method to let Symfony guess the right extension according to the file MIME type.
+#. The ``UploadedFile`` class also provides a :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::move()`
+ method to store the file in its intended directory. Defining this directory
+ path as an application configuration option is considered a good practice that
+ simplifies the code: ``$this->container->getParameter('brochures_dir')``.
+
+You can now use the following code to link to the PDF brochure of an product:
+
+.. code-block:: html+jinja
+
+ View brochure (PDF)
+
+.. _`VichUploaderBundle`: https://github.com/dustin10/VichUploaderBundle
diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc
index 2c4d8eb94f6..6c37de6d5f7 100644
--- a/cookbook/map.rst.inc
+++ b/cookbook/map.rst.inc
@@ -50,6 +50,7 @@
* :doc:`/cookbook/controller/error_pages`
* :doc:`/cookbook/controller/service`
+ * :doc:`/cookbook/controller/upload_file`
* **Debugging**