-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added cookbook to show how to make a simple upload #4018
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
.. index:: | ||
single: Controller; Upload; File | ||
|
||
How to upload files | ||
=================== | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should immediately say that there's a great bundle called VichUploaderBundle that makes uploading a breeze if you're using Doctrine or Propel. If you want to learn about how to handle uploads manually, this post is for you. |
||
Let's begin with the creation of an entity Product having a document property to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We never use the first person in the ddocumentation. You should replace "Let's" here. E.g. "First of all, you need to create a |
||
which will contain the description of that product. We'll also indicate the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here: "You'll also want to indicate [...]" |
||
validation needed for each properties of the entity. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [...] each property [...] |
||
|
||
So let's say we have a product with a name, a price and a document which must be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Assume you want a product with a name, a price and a PDF document." You get the trick now, I think. You should remove all first persons in the rest of the article :) |
||
a PDF file:: | ||
|
||
// src/Vendor/ShopBundle/Entity/Product.php | ||
namespace Vendor\ShopBundle\Entity; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use |
||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we only really need to show the |
||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bit unclear for me. What about using "The |
||
To know more about validation, take a look at the :doc:`validation book </book/validation>` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would put this in a |
||
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:: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The double colon is a shortcut for: This is some PHP code:
.. code-block:: php
echo 'hello'; That's not what you wanted here, so you should use a single colon instead. |
||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# src/Vendor/ShopBundle/Resources/config/services.yml | ||
services: | ||
vendor.form.product_type: | ||
class: Vendor\ShopBundle\Form\ProductType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove all spaces except one between the key and the value. |
||
tags: | ||
- { name: form.type } | ||
|
||
# Import the services.yml file of your bundle in your config.yml | ||
imports: | ||
- { resource: "@VendorShopBundle/Resources/config/services.yml" } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe people will understand this. You should put it in a seperate code block and also document XML and PHP. |
||
|
||
.. code-block:: xml | ||
|
||
<!-- src/Vendor/ShopBundle/Resources/config/services.xml --> | ||
<services> | ||
<service id="vendor.form.product_type" class="Vendor\ShopBundle\Form\ProductType"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To comply to the standards, this should be |
||
<tag name="form.type" alias="product" /> | ||
</service> | ||
</services> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to have a complete XML document: <?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services">
<services>
<!-- ... the service definition -->
<services>
</container> |
||
|
||
.. code-block:: php | ||
|
||
// src/Vendor/ShopBundle/DependencyInjection/VendorShopExtension.php | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
//… | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We always use 3 normal dots instead of the (correct) ellipsis. |
||
|
||
$definition = new Definition('Vendor\ShopBundle\Form\ProductType'); | ||
$container->setDefinition('vendor.form.product_type', $definition); | ||
$definition->addTag('form.type'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would move this line before the |
||
|
||
If you never dealt with services before, take some time to read the | ||
:doc:`book Service </book/service_container>` chapter. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would put this in a |
||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should remove one empty line |
||
We must display the form to our users. To do that, create the controller as | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say "The form has to be shown to the users. To do that [...]" |
||
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"}) | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like |
||
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:: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have to remove one of the colons so that the following lines are not treated as a PHP code block. |
||
|
||
.. code-block:: html+jinja | ||
|
||
{# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} | ||
{% form_theme form _self %} | ||
|
||
<h1>Creation of a new Product</h1> | ||
|
||
<form action="{{ path('vendor_product_new') }}" method="POST" {{ form_enctype(form) }}> | ||
{{ form_widget(form) }} | ||
</form> | ||
|
||
{% block form_row %} | ||
{% spaceless %} | ||
<fieldset> | ||
<legend>{{ form_label(form) }}</legend> | ||
{{ form_errors(form) }} | ||
|
||
{{ form_widget(form) }} | ||
</fieldset> | ||
{% endspaceless %} | ||
{% endblock form_row %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also need to show the PHP template. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should show the |
||
|
||
We added some sugar by adapting our form with a form theme (take a look at the | ||
:doc:`form themes </cookbook/form/form_customization#what-are-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 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should add |
||
class ProductController extends Controller | ||
{ | ||
/** | ||
* @Route("/product/new", name="vendor_product_new") | ||
* @Template() | ||
* @Method({"GET", "POST"}) | ||
*/ | ||
public function newAction(Request $request) | ||
{ | ||
//… | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use 3 dots here |
||
|
||
if ($form->isValid()) { | ||
|
||
$file = $product->getDocument() | ||
|
||
// Compute the name of the file. | ||
$name = md5(uniqid()).'.'.$file->guessExtension(); | ||
|
||
$file = $file->move(__DIR__.'/../../../../web/uploads', $name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use |
||
$product->setDocument($filename); | ||
|
||
// Perform some persistance | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
$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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the indent here |
||
|
||
{# src/Vendor/ShopBundle/Resources/views/Product/new.html.twig #} | ||
|
||
{# … #} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use normal three dots here and below |
||
{% for flashes in app.session.flashbag.all %} | ||
{% for flashMessage in flashes %} | ||
<ul> | ||
<li>{{ flashMessage }}</li> | ||
</ul> | ||
{% endfor %} | ||
{% endfor %} | ||
{# … #} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should also include a PHP example |
||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be "How to Upload Files" (all words should be capitialized, except from closed class words)