From 888c61c44ec7dfd7c61904be4e70afd30532ad24 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Wed, 16 Dec 2015 23:35:20 +0100 Subject: [PATCH] Remove old File Upload article + improve the new one --- book/forms.rst | 9 +- cookbook/controller/upload_file.rst | 315 +++++++++- cookbook/doctrine/file_uploads.rst | 575 ------------------- cookbook/doctrine/index.rst | 1 - cookbook/form/create_form_type_extension.rst | 22 +- cookbook/map.rst.inc | 3 +- redirection_map | 1 + reference/forms/types/file.rst | 2 +- 8 files changed, 310 insertions(+), 618 deletions(-) delete mode 100644 cookbook/doctrine/file_uploads.rst diff --git a/book/forms.rst b/book/forms.rst index f0b4a2945eb..4afd4827093 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -1953,10 +1953,9 @@ HTML form so that the user can modify that data. The second goal of a form is to take the data submitted by the user and to re-apply it to the object. There's still much more to learn about the powerful world of forms, such as -how to handle -:doc:`file uploads with Doctrine ` or how -to create a form where a dynamic number of sub-forms can be added (e.g. a -todo list where you can keep adding more fields via JavaScript before submitting). +how to handle :doc:`file uploads ` or how to +create a form where a dynamic number of sub-forms can be added (e.g. a todo +list where you can keep adding more fields via JavaScript before submitting). See the cookbook for these topics. Also, be sure to lean on the :doc:`field type reference documentation `, which includes examples of how to use each field type and its options. @@ -1964,7 +1963,7 @@ includes examples of how to use each field type and its options. Learn more from the Cookbook ---------------------------- -* :doc:`/cookbook/doctrine/file_uploads` +* :doc:`/cookbook/controller/upload_file` * :doc:`File Field Reference ` * :doc:`Creating Custom Field Types ` * :doc:`/cookbook/form/form_customization` diff --git a/cookbook/controller/upload_file.rst b/cookbook/controller/upload_file.rst index 065b4bbaf0a..344fe82c4c8 100644 --- a/cookbook/controller/upload_file.rst +++ b/cookbook/controller/upload_file.rst @@ -86,16 +86,27 @@ 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+twig +.. configuration-block:: - {# app/Resources/views/product/new.html.twig #} -

Adding a new product

+ .. code-block:: html+twig - {{ form_start() }} - {# ... #} + {# app/Resources/views/product/new.html.twig #} +

Adding a new product

- {{ form_row(form.brochure) }} - {{ form_end() }} + {{ form_start(form) }} + {# ... #} + + {{ form_row(form.brochure) }} + {{ form_end(form) }} + + .. code-block:: html+php + + +

Adding a new product

+ + start($form) ?> + row($form['brochure']) ?> + end($form) ?> Finally, you need to update the code of the controller that handles the form:: @@ -119,7 +130,7 @@ Finally, you need to update the code of the controller that handles the form:: $form = $this->createForm(new ProductType(), $product); $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { // $file stores the uploaded PDF file /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */ $file = $product->getBrochure(); @@ -128,8 +139,10 @@ Finally, you need to update the code of the controller that handles the form:: $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); + $file->move( + $this->container->getParameter('brochures_directory'), + $fileName + ); // Update the 'brochure' property to store the PDF file name // instead of its contents @@ -146,16 +159,27 @@ Finally, you need to update the code of the controller that handles the form:: } } +Now, create the ``brochures_directory`` parameter that was used in the +controller to specify the directory in which the brochures should be stored: + +.. code-block:: yaml + + # app/config/config.yml + + # ... + parameters: + brochures_directory: '%kernel.root_dir%/../web/uploads/brochures' + 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 + :class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class. This class 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`` + users. This also applies to the files uploaded by your visitors. The ``UploadedFile`` 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::getClientSize`) @@ -164,15 +188,268 @@ There are some important things to consider in the code of the above controller: 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: +You can use the following code to link to the PDF brochure of a product: + +.. configuration-block:: + + .. code-block:: html+twig + + View brochure (PDF) + + .. code-block:: html+php + + + View brochure (PDF) + + +.. tip:: + + When creating a form to edit an already persisted item, the file form type + still expects a :class:`Symfony\\Component\\HttpFoundation\\File\\File` + instance. As the persisted entity now contains only the relative file path, + you first have to concatenate the configured upload path with the stored + filename and create a new ``File`` class:: + + use Symfony\Component\HttpFoundation\File\File; + // ... + + $product->setBrochure( + new File($this->getParameter('brochures_directory').'/'.$product->getBrochure()) + ); + +Creating an Uploader Service +---------------------------- + +To avoid logic in controllers, making them big, you can extract the upload +logic to a seperate service:: + + // src/AppBundle/FileUploader.php + namespace AppBundle; + + use Symfony\Component\HttpFoundation\File\UploadedFile; + + class FileUploader + { + private $targetDir; + + public function __construct($targetDir) + { + $this->targetDir = $targetDir; + } + + public function upload(UploadedFile $file) + { + $fileName = md5(uniqid()).'.'.$file->guessExtension(); + + $file->move($this->targetDir, $fileName); + + return $fileName; + } + } + +Then, define a service for this class: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + app.brochure_uploader: + class: AppBundle\FileUploader + arguments: ['%brochures_directory%'] + + .. code-block:: xml + + + + + + + + %brochures_directory% + + + + .. code-block:: php + + // app/config/services.php + use Symfony\Component\DependencyInjection\Definition; + + // ... + $container->setDefinition('app.brochure_uploader', new Definition( + 'AppBundle\FileUploader', + array('%brochures_directory%') + )); + +Now you're ready to use this service in the controller:: + + // src/AppBundle/Controller/ProductController.php + + // ... + public function newAction(Request $request) + { + // ... + + if ($form->isValid()) { + $file = $product->getBrochure(); + $fileName = $this->get('app.brochure_uploader')->upload($file); + + $product->setBrochure($fileName); -.. code-block:: html+twig + // ... + } + + // ... + } + +Using a Doctrine Listener +------------------------- + +If you are using Doctrine to store the Product entity, you can create a +:doc:`Doctrine listener ` to +automatically upload the file when persisting the entity:: + + // src/AppBundle/EventListener/BrochureUploadListener.php + namespace AppBundle\EventListener; + + use Symfony\Component\HttpFoundation\File\UploadedFile; + use Doctrine\ORM\Event\LifecycleEventArgs; + use Doctrine\ORM\Event\PreUpdateEventArgs; + use AppBundle\Entity\Product; + use AppBundle\FileUploader; + + class BrochureUploadListener + { + private $uploader; + + public function __construct(FileUploader $uploader) + { + $this->uploader = $uploader; + } + + public function prePersist(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + $this->uploadFile($entity); + } + + public function preUpdate(PreUpdateEventArgs $args) + { + $entity = $args->getEntity(); + + $this->uploadFile($entity); + } + + private function uploadFile($entity) + { + // upload only works for Product entities + if (!$entity instanceof Product) { + return; + } + + $file = $entity->getBrochure(); + + // only upload new files + if (!$file instanceof UploadedFile) { + return; + } + + $fileName = $this->uploader->upload($file); + $entity->setBrochure($fileName); + } + } + +Now, register this class as a Doctrine listener: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + app.doctrine_brochure_listener: + class: AppBundle\EventListener\BrochureUploadListener + arguments: ['@app.brochure_uploader'] + tags: + - { name: doctrine.event_listener, event: prePersist } + - { name: doctrine.event_listener, event: preUpdate } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/services.php + use Symfony\Component\DependencyInjection\Reference; + + // ... + $definition = new Definition( + 'AppBundle\EventListener\BrochureUploaderListener', + array(new Reference('brochures_directory')) + ); + $definition->addTag('doctrine.event_listener', array( + 'event' => 'prePersist', + )); + $definition->addTag('doctrine.event_listener', array( + 'event' => 'preUpdate', + )); + $container->setDefinition('app.doctrine_brochure_listener', $definition); + +This listeners is now automatically executed when persisting a new Product +entity. This way, you can remove everything related to uploading from the +controller. + +.. tip:: + + This listener can also create the ``File`` instance based on the path when + fetching entities from the database:: + + // ... + use Symfony\Component\HttpFoundation\File\File; + + // ... + class BrochureUploadListener + { + // ... + + public function postLoad(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + $fileName = $entity->getBrochure(); + + $entity->setBrochure(new File($this->targetPath.'/'.$fileName)); + } + } - View brochure (PDF) + After adding these lines, configure the listener to also listen for the + ``postLoad`` event. .. _`VichUploaderBundle`: https://github.com/dustin10/VichUploaderBundle diff --git a/cookbook/doctrine/file_uploads.rst b/cookbook/doctrine/file_uploads.rst deleted file mode 100644 index 4e086ed699e..00000000000 --- a/cookbook/doctrine/file_uploads.rst +++ /dev/null @@ -1,575 +0,0 @@ -.. index:: - single: Doctrine; File uploads - -How to Handle File Uploads with Doctrine -======================================== - -.. 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. - -Handling file uploads with Doctrine entities is no different than handling -any other file upload. In other words, you're free to move the file in your -controller after handling a form submission. For examples of how to do this, -see the :doc:`file type reference ` page. - -If you choose to, you can also integrate the file upload into your entity -lifecycle (i.e. creation, update and removal). In this case, as your entity -is created, updated, and removed from Doctrine, the file uploading and removal -processing will take place automatically (without needing to do anything in -your controller). - -To make this work, you'll need to take care of a number of details, which -will be covered in this cookbook entry. - -Basic Setup ------------ - -First, create a simple Doctrine entity class to work with:: - - // src/AppBundle/Entity/Document.php - namespace AppBundle\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'; - } - } - -The ``Document`` entity has a name and it is associated with a file. The ``path`` -property stores the relative path to the file and is persisted to the database. -The ``getAbsolutePath()`` is a convenience method that returns the absolute -path to the file while the ``getWebPath()`` is a convenience method that -returns the web path, which can be used in a template to link to the uploaded -file. - -.. tip:: - - If you have not done so already, you should probably read the - :doc:`file ` type documentation first to - understand how the basic upload process works. - -.. note:: - - If you're using annotations to specify your validation rules (as shown - in this example), be sure that you've enabled validation by annotation - (see :ref:`validation configuration `). - -.. caution:: - - If you use the ``getUploadRootDir()`` method, be aware that this will save - the file inside the document root, which can be accessed by everyone. - Consider placing it out of the document root and adding custom viewing - logic when you need to secure the files. - -To handle the actual file upload in the form, use a "virtual" ``file`` field. -For example, if you're building your form directly in a controller, it might -look like this:: - - public function uploadAction() - { - // ... - - $form = $this->createFormBuilder($document) - ->add('name') - ->add('file') - ->getForm(); - - // ... - } - -Next, create this property on your ``Document`` class and add some validation -rules:: - - 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; - } - } - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/AppBundle/Entity/Document.php - namespace AppBundle\Entity; - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Document - { - /** - * @Assert\File(maxSize="6000000") - */ - private $file; - - // ... - } - - .. code-block:: yaml - - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\Document: - properties: - file: - - File: - maxSize: 6000000 - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // src/AppBundle/Entity/Document.php - namespace AppBundle\Entity; - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Document - { - // ... - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('file', new Assert\File(array( - 'maxSize' => 6000000, - ))); - } - } - -.. note:: - - As you are using the ``File`` constraint, Symfony will automatically guess - that the form field is a file upload input. That's why you did not have - to set it explicitly when creating the form above (``->add('file')``). - -The following controller shows you how to handle the entire process:: - - // ... - use AppBundle\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()); - } - -The previous controller will automatically persist the ``Document`` entity -with the submitted name, but it will do nothing about the file and the ``path`` -property will be blank. - -An easy way to handle the file upload is to move it just before the entity is -persisted and then set the ``path`` property accordingly. Start by calling -a new ``upload()`` method on the ``Document`` class, which you'll create -in a moment to handle the file upload:: - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - - $document->upload(); - - $em->persist($document); - $em->flush(); - - return $this->redirect(...); - } - -The ``upload()`` method will take advantage of the :class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` -object, which is what's returned after a ``file`` field is submitted:: - - public function upload() - { - // the file property can be empty if the field is not required - 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 - $this->path = $this->getFile()->getClientOriginalName(); - - // clean up the file property as you won't need it anymore - $this->file = null; - } - -Using Lifecycle Callbacks -------------------------- - -.. caution:: - - Using lifecycle callbacks is a limited technique that has some drawbacks. - If you want to remove the hardcoded ``__DIR__`` reference inside - the ``Document::getUploadRootDir()`` method, the best way is to start - using explicit :doc:`doctrine listeners `. - There you will be able to inject kernel parameters such as ``kernel.root_dir`` - to be able to build absolute paths. - -Even if this implementation works, it suffers from a major flaw: What if there -is a problem when the entity is persisted? The file would have already moved -to its final location even though the entity's ``path`` property didn't -persist correctly. - -To avoid these issues, you should change the implementation so that the database -operation and the moving of the file become atomic: if there is a problem -persisting the entity or if the file cannot be moved, then *nothing* should -happen. - -To do this, you need to move the file right as Doctrine persists the entity -to the database. This can be accomplished by hooking into an entity lifecycle -callback:: - - /** - * @ORM\Entity - * @ORM\HasLifecycleCallbacks - */ - class Document - { - } - -Next, refactor the ``Document`` class to take advantage of these callbacks:: - - 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 - $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); - } - } - } - -.. caution:: - - If changes to your entity are handled by a Doctrine event listener or event - subscriber, the ``preUpdate()`` callback must notify Doctrine about the changes - being done. - For full reference on preUpdate event restrictions, see `preUpdate`_ in the - Doctrine Events documentation. - -The class now does everything you need: it generates a unique filename before -persisting, moves the file after persisting, and removes the file if the -entity is ever deleted. - -Now that the moving of the file is handled atomically by the entity, the -call to ``$document->upload()`` should be removed from the controller:: - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - - $em->persist($document); - $em->flush(); - - return $this->redirect(...); - } - -.. note:: - - The ``@ORM\PrePersist()`` and ``@ORM\PostPersist()`` event callbacks are - triggered before and after the entity is persisted to the database. On the - other hand, the ``@ORM\PreUpdate()`` and ``@ORM\PostUpdate()`` event - callbacks are called when the entity is updated. - -.. caution:: - - The ``PreUpdate`` and ``PostUpdate`` callbacks are only triggered if there - is a change in one of the entity's fields that are persisted. This means - that, by default, if you modify only the ``$file`` property, these events - will not be triggered, as the property itself is not directly persisted - via Doctrine. One solution would be to use an ``updated`` field that's - persisted to Doctrine, and to modify it manually when changing the file. - -Using the ``id`` as the Filename --------------------------------- - -If you want to use the ``id`` as the name of the file, the implementation is -slightly different as you need to save the extension under the ``path`` -property, instead of the actual filename:: - - 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(); - $this->path = null; - } 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() - { - if (isset($this->temp)) { - unlink($this->temp); - } - } - - public function getAbsolutePath() - { - return null === $this->path - ? null - : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path; - } - } - -You'll notice in this case that you need to do a little bit more work in -order to remove the file. Before it's removed, you must store the file path -(since it depends on the id). Then, once the object has been fully removed -from the database, you can safely delete the file (in ``PostRemove``). - -.. _`preUpdate`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate -.. _`VichUploaderBundle`: https://github.com/dustin10/VichUploaderBundle diff --git a/cookbook/doctrine/index.rst b/cookbook/doctrine/index.rst index d14ecb20536..e52b2aecb72 100644 --- a/cookbook/doctrine/index.rst +++ b/cookbook/doctrine/index.rst @@ -4,7 +4,6 @@ Doctrine .. toctree:: :maxdepth: 2 - file_uploads common_extensions event_listeners_subscribers dbal diff --git a/cookbook/form/create_form_type_extension.rst b/cookbook/form/create_form_type_extension.rst index 39e23350f80..671cc379311 100644 --- a/cookbook/form/create_form_type_extension.rst +++ b/cookbook/form/create_form_type_extension.rst @@ -134,10 +134,9 @@ Adding the extension Business Logic The goal of your extension is to display nice images next to file inputs (when the underlying model contains images). For that purpose, suppose that you use an approach similar to the one described in -:doc:`How to handle File Uploads with Doctrine `: -you have a Media model with a file property (corresponding to the file field -in the form) and a path property (corresponding to the image path in the -database):: +:doc:`How to handle File Uploads with Doctrine `: +you have a Media model with a path property, corresponding to the image path in +the database:: // src/AppBundle/Entity/Media.php namespace AppBundle\Entity; @@ -153,12 +152,6 @@ database):: */ private $path; - /** - * @var \Symfony\Component\HttpFoundation\File\UploadedFile - * @Assert\File(maxSize="2M") - */ - public $file; - // ... /** @@ -179,8 +172,8 @@ the ``file`` form type: #. Override the ``setDefaultOptions`` method in order to add an ``image_path`` option; -#. Override the ``buildForm`` and ``buildView`` methods in order to pass the image - URL to the view. +#. Override the ``buildView`` methods in order to pass the image URL to the + view. The logic is the following: when adding a form field of type ``file``, you will be able to specify a new option: ``image_path``. This option will @@ -227,14 +220,13 @@ it in the view:: */ public function buildView(FormView $view, FormInterface $form, array $options) { - if (array_key_exists('image_path', $options)) { + if (isset($options['image_path'])) { $parentData = $form->getParent()->getData(); + $imageUrl = null; if (null !== $parentData) { $accessor = PropertyAccess::createPropertyAccessor(); $imageUrl = $accessor->getValue($parentData, $options['image_path']); - } else { - $imageUrl = null; } // set an "image_url" variable that will be available when rendering this field diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 57e619eceff..1db1489c82a 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -68,7 +68,6 @@ * :doc:`/cookbook/doctrine/index` - * :doc:`/cookbook/doctrine/file_uploads` * :doc:`/cookbook/doctrine/common_extensions` * :doc:`/cookbook/doctrine/event_listeners_subscribers` * :doc:`/cookbook/doctrine/dbal` @@ -111,7 +110,7 @@ * :doc:`/cookbook/form/use_empty_data` * :doc:`/cookbook/form/direct_submit` * (Validation) :doc:`/cookbook/validation/custom_constraint` - * (Doctrine) :doc:`/cookbook/doctrine/file_uploads` + * (Controller) :doc:`/cookbook/controller/upload_file` * :doc:`/cookbook/frontend/index` diff --git a/redirection_map b/redirection_map index 6e8ea5d953f..d03639a3991 100644 --- a/redirection_map +++ b/redirection_map @@ -39,3 +39,4 @@ /create_framework/http-kernel-httpkernelinterface /create_framework/http_kernel_httpkernelinterface /create_framework/http-kernel-httpkernel-class /create_framework/http_kernel_httpkernel_class /create_framework/dependency-injection /create_framework/dependency_injection +/cookbook/doctrine/file_uploads /cookbook/controller/upload_file diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst index 4b5faaa41d7..6b61a0387b9 100644 --- a/reference/forms/types/file.rst +++ b/reference/forms/types/file.rst @@ -74,7 +74,7 @@ could have been manipulated by the end-user. Moreover, it can contain characters that are not allowed in file names. You should sanitize the name before using it directly. -Read the :doc:`cookbook ` for an example +Read the :doc:`cookbook ` for an example of how to manage a file upload associated with a Doctrine entity. Overridden Options