From 1bd2d8ba18a7a9eb36f80555cea6cb5fe0253d5c Mon Sep 17 00:00:00 2001 From: Saro0h Date: Fri, 11 Jul 2014 16:17:01 +0200 Subject: [PATCH 1/2] Added cookbook to show how to make a simple upload (ref to #2346 issue) -- -- --- cookbook/controller/upload_file.rst | 270 ++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 cookbook/controller/upload_file.rst diff --git a/cookbook/controller/upload_file.rst b/cookbook/controller/upload_file.rst new file mode 100644 index 00000000000..9e0534a3f31 --- /dev/null +++ b/cookbook/controller/upload_file.rst @@ -0,0 +1,270 @@ +.. index:: + single: Controller; Upload; File + +How to upload files +=================== + +Let's begin with the creation of an entity Product having a document property to +which will contain the description of that product. We'll also indicate the +validation needed for each properties of the entity. + +So let's say we have a product with a name, a price and a document which must be +a PDF file:: + + // src/Vendor/ShopBundle/Entity/Product.php + namespace Vendor\ShopBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Product + { + /** + * @Assert\NotBlank(message="You must indicate a name to your product.") + */ + private $name; + + /** + * @Assert\NotBlank(message="You must indicate a price to your product.") + * @Assert\Type(type="float", message="Amount must be a valid number.") + */ + private $price; + + /** + * @Assert\NotBlank(message="You must upload a description with a PDF file.") + * @Assert\File(mimeTypes={ "application/pdf" }) + */ + private $document; + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + + return $this; + } + + public function getPrice() + { + return $this->price; + } + + public function setPrice($price) + { + $this->price = $price; + + return $this; + } + + public function getDocument() + { + return $this->document; + } + + public function setDocument($document) + { + $this->document = $document; + + return $this; + } + } + +We also made sure that the user will have to indicate information to each fields. +To know more about validation, take a look at the :doc:`validation book ` +chapter. + +You have now to create the ``ProductType`` with those three fields as following:: + + // src/Vendor/ShopBundle/Form/ProductType.php + namespace Vendor\ShopBundle\Form; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + + class ProductType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('name', 'text', array('label' => 'Name:')) + ->add('price', 'money', array('label' => 'Price:')) + ->add('document', 'file', array('label' => 'Upload description (PDF file):')) + ->add('submit', 'submit', array('label' => 'Create!')) + ; + } + + public function getName() + { + return 'product'; + } + } + +Now, make it as a service so it can be used anywhere easily:: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Vendor/ShopBundle/Resources/config/services.yml + services: + vendor.form.product_type: + class: Vendor\ShopBundle\Form\ProductType + tags: + - { name: form.type } + + # Import the services.yml file of your bundle in your config.yml + imports: + - { resource: "@VendorShopBundle/Resources/config/services.yml" } + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // src/Vendor/ShopBundle/DependencyInjection/VendorShopExtension.php + use Symfony\Component\DependencyInjection\Definition; + + //… + + $definition = new Definition('Vendor\ShopBundle\Form\ProductType'); + $container->setDefinition('vendor.form.product_type', $definition); + $definition->addTag('form.type'); + +If you never dealt with services before, take some time to read the +:doc:`book Service ` chapter. + + +We must display the form to our users. To do that, create the controller as +following:: + + // src/Vendor/ShopBundle/Controller/ProductController.php + namespace Vendor\ShopBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Component\HttpFoundation\Request; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; + use Vendor\ShopBundle\Entity\Product; + + class ProductController extends Controller + { + /** + * @Route("/product/new", name="vendor_product_new") + * @Template() + * @Method({"GET", "POST"}) + */ + public function newAction(Request $request) + { + $product = new Product(); + $form = $this->createForm('product', $product); + $form->handleRequest($request); + + return array('form' => $form->createView()); + } + } + +Create the corresponding template as following:: + +.. code-block:: html+jinja + + {# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} + {% form_theme form _self %} + +

Creation of a new Product

+ +
+ {{ form_widget(form) }} +
+ + {% block form_row %} + {% spaceless %} +
+ {{ form_label(form) }} + {{ form_errors(form) }} + + {{ form_widget(form) }} +
+ {% endspaceless %} + {% endblock form_row %} + +We added some sugar by adapting our form with a form theme (take a look at the +:doc:`form themes ` to +know more about the subject). + +We now have our form displayed. Let's complete our action to deal with the +upload of our document:: + + // src/Vendor/ShopBundle/Controller/ProductController.php + + class ProductController extends Controller + { + /** + * @Route("/product/new", name="vendor_product_new") + * @Template() + * @Method({"GET", "POST"}) + */ + public function newAction(Request $request) + { + //… + + if ($form->isValid()) { + + $file = $product->getDocument() + + // Compute the name of the file. + $name = md5(uniqid()).'.'.$file->guessExtension(); + + $file = $file->move(__DIR__.'/../../../../web/uploads', $name); + $product->setDocument($filename); + + // Perform some persistance + + $this->getSession()->getFlashBag()->add('notice', 'The upload has been well uploaded.'); + + return $this->redirect($this->generateUrl('vendor_product_new')); + } + + return array('form' => $form->createView()); + } + } + +The :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension()` +returns the extension of the file the user just uploaded. + +Note the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::move` +method allowing movement of the file + +We must display the flash message in our template:: + + .. code-block:: html+jinja + + {# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} + + {# … #} + {% for flashes in app.session.flashbag.all %} + {% for flashMessage in flashes %} + + {% endfor %} + {% endfor %} + {# … #} + +The file is now uploaded in the folder ``web/upload`` of your project. + +.. note:: + + For the sake of testability and maintainability, it is recommended to put the + logic inherent to the upload in a dedicated service. You could even make the + path to the upload folder as a configuration parameter injected to your service. + That way, you make the upload feature more flexible. From d218baa8f74b83e3ea1b2132324034398675bb2c Mon Sep 17 00:00:00 2001 From: Saro0h Date: Sun, 13 Jul 2014 15:31:01 +0200 Subject: [PATCH 2/2] [WIP] Fixed parts of the cookbook according to comments --- cookbook/controller/index.rst | 1 + cookbook/controller/upload_file.rst | 105 +++++++++++++++------------- cookbook/map.rst.inc | 1 + 3 files changed, 60 insertions(+), 47 deletions(-) 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 index 9e0534a3f31..3296359165b 100644 --- a/cookbook/controller/upload_file.rst +++ b/cookbook/controller/upload_file.rst @@ -1,18 +1,21 @@ .. index:: single: Controller; Upload; File -How to upload files +How to Upload Files =================== Let's begin with the creation of an entity Product having a document property to -which will contain the description of that product. We'll also indicate the +which will contain the description of that product. + +First of all, you need to create a `Product` entity that has a `document` property +which will contain the description of the product. You'll also indicate the validation needed for each properties of the entity. -So let's say we have a product with a name, a price and a document which must be -a PDF file:: +Assume you need to have a product with a name, a price and a document which must +be a PDF file:: - // src/Vendor/ShopBundle/Entity/Product.php - namespace Vendor\ShopBundle\Entity; + // src/Acme/ShopBundle/Entity/Product.php + namespace Acme\ShopBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; @@ -72,14 +75,18 @@ a PDF file:: } } -We also made sure that the user will have to indicate information to each fields. -To know more about validation, take a look at the :doc:`validation book ` -chapter. +To make sure that the user will have to indicate information to each fields by +adding the `NotBlank` constraint. + +.. seealso:: + + To know more about validation, take a look at the :doc:`validation book ` + chapter. You have now to create the ``ProductType`` with those three fields as following:: - // src/Vendor/ShopBundle/Form/ProductType.php - namespace Vendor\ShopBundle\Form; + // src/Acme/ShopBundle/Form/ProductType.php + namespace Acme\ShopBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -102,64 +109,68 @@ You have now to create the ``ProductType`` with those three fields as following: } } -Now, make it as a service so it can be used anywhere easily:: +Now, make it as a service so it can be used anywhere easily: .. configuration-block:: .. code-block:: yaml - # src/Vendor/ShopBundle/Resources/config/services.yml + # src/Acme/ShopBundle/Resources/config/services.yml services: - vendor.form.product_type: - class: Vendor\ShopBundle\Form\ProductType + acme.form.product_type: + class: Acme\ShopBundle\Form\ProductType tags: - { name: form.type } # Import the services.yml file of your bundle in your config.yml imports: - - { resource: "@VendorShopBundle/Resources/config/services.yml" } + - { resource: "@AcmeShopBundle/Resources/config/services.yml" } .. code-block:: xml - + + + + - + .. code-block:: php - // src/Vendor/ShopBundle/DependencyInjection/VendorShopExtension.php + // src/Acme/ShopBundle/DependencyInjection/AcmeShopExtension.php use Symfony\Component\DependencyInjection\Definition; - //… + //... - $definition = new Definition('Vendor\ShopBundle\Form\ProductType'); - $container->setDefinition('vendor.form.product_type', $definition); + $definition = new Definition('Acme\ShopBundle\Form\ProductType'); $definition->addTag('form.type'); + $container->setDefinition('acme.form.product_type', $definition); -If you never dealt with services before, take some time to read the -:doc:`book Service ` chapter. +.. seealso:: + If you never dealt with services before, take some time to read the + :doc:`book Service ` chapter. -We must display the form to our users. To do that, create the controller as +Now, time to display the form to our users. To do that, create the controller as following:: - // src/Vendor/ShopBundle/Controller/ProductController.php - namespace Vendor\ShopBundle\Controller; + // src/Acme/ShopBundle/Controller/ProductController.php + namespace Acme\ShopBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; - use Vendor\ShopBundle\Entity\Product; + use Acme\ShopBundle\Entity\Product; class ProductController extends Controller { /** - * @Route("/product/new", name="vendor_product_new") + * @Route("/product/new", name="acme_product_new") * @Template() * @Method({"GET", "POST"}) */ @@ -173,16 +184,16 @@ following:: } } -Create the corresponding template as following:: +Then, create the corresponding template as following: .. code-block:: html+jinja - {# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} + {# src/Acme/ShopBundle/Resources/views/Product/new.html.twig #} {% form_theme form _self %}

Creation of a new Product

-
+ {{ form_widget(form) }}
@@ -197,25 +208,25 @@ Create the corresponding template as following:: {% endspaceless %} {% endblock form_row %} -We added some sugar by adapting our form with a form theme (take a look at the -:doc:`form themes ` to -know more about the subject). +Some sugar has been added by adapting our form with a form theme (take a look at +the :doc:`form themes ` +to know more about the subject). -We now have our form displayed. Let's complete our action to deal with the +The form is now displayed. You have to complete our action to deal with the upload of our document:: - // src/Vendor/ShopBundle/Controller/ProductController.php + // src/Acme/ShopBundle/Controller/ProductController.php class ProductController extends Controller { /** - * @Route("/product/new", name="vendor_product_new") + * @Route("/product/new", name="acme_product_new") * @Template() * @Method({"GET", "POST"}) */ public function newAction(Request $request) { - //… + //... if ($form->isValid()) { @@ -227,11 +238,11 @@ upload of our document:: $file = $file->move(__DIR__.'/../../../../web/uploads', $name); $product->setDocument($filename); - // Perform some persistance + // ... perform some persistance $this->getSession()->getFlashBag()->add('notice', 'The upload has been well uploaded.'); - return $this->redirect($this->generateUrl('vendor_product_new')); + return $this->redirect($this->generateUrl('acme_product_new')); } return array('form' => $form->createView()); @@ -242,15 +253,15 @@ The :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtens returns the extension of the file the user just uploaded. Note the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::move` -method allowing movement of the file +method allowing movement of the file. -We must display the flash message in our template:: +To display the flash message in our template, you have to add the following code: - .. code-block:: html+jinja +.. code-block:: html+jinja - {# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} + {# src/Acme/ShopBundle/Resources/views/Product/new.html.twig #} - {# … #} + {# ... #} {% for flashes in app.session.flashbag.all %} {% for flashMessage in flashes %}
    @@ -258,7 +269,7 @@ We must display the flash message in our template::
{% endfor %} {% endfor %} - {# … #} + {# ... #} The file is now uploaded in the folder ``web/upload`` of your project. diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index ff7a2530b6f..f65ab9f6b7b 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -43,6 +43,7 @@ * :doc:`/cookbook/controller/error_pages` * :doc:`/cookbook/controller/service` + * :doc:`/cookbook/controller/upload_file` * **Debugging**