Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit758e083

Browse files
committed
feature#6040 Remove old File Upload article + improve the new one (WouterJ)
This PR was squashed before being merged into the 2.3 branch (closes#6040).Discussion----------Remove old File Upload article + improve the new one| Q | A| --- | ---| Doc fix? | yes| New docs? | yes| Applies to | 2.3+| Fixed tickets |#5375The old file upload article wasn't good, a new one was written by@javiereguiluz but the old one remained online. This PR removes the old one and documents the missing bits in the new article.Commits-------888c61c Remove old File Upload article + improve the new one
2 parentsb9a3606 +888c61c commit758e083

File tree

8 files changed

+310
-618
lines changed

8 files changed

+310
-618
lines changed

‎book/forms.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,18 +1953,17 @@ HTML form so that the user can modify that data. The second goal of a form is to
19531953
take the data submitted by the user and to re-apply it to the object.
19541954

19551955
There's still much more to learn about the powerful world of forms, such as
1956-
how to handle
1957-
:doc:`file uploads with Doctrine</cookbook/doctrine/file_uploads>` or how
1958-
to create a form where a dynamic number of sub-forms can be added (e.g. a
1959-
todo list where you can keep adding more fields via JavaScript before submitting).
1956+
how to handle:doc:`file uploads</cookbook/controller/upload_file>` or how to
1957+
create a form where a dynamic number of sub-forms can be added (e.g. a todo
1958+
list where you can keep adding more fields via JavaScript before submitting).
19601959
See the cookbook for these topics. Also, be sure to lean on the
19611960
:doc:`field type reference documentation</reference/forms/types>`, which
19621961
includes examples of how to use each field type and its options.
19631962

19641963
Learn more from the Cookbook
19651964
----------------------------
19661965

1967-
*:doc:`/cookbook/doctrine/file_uploads`
1966+
*:doc:`/cookbook/controller/upload_file`
19681967
*:doc:`File Field Reference</reference/forms/types/file>`
19691968
*:doc:`Creating Custom Field Types</cookbook/form/create_custom_field_type>`
19701969
*:doc:`/cookbook/form/form_customization`

‎cookbook/controller/upload_file.rst

Lines changed: 296 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,27 @@ Now, update the template that renders the form to display the new ``brochure``
8686
field (the exact template code to add depends on the method used by your application
8787
to:doc:`customize form rendering</cookbook/form/form_customization>`):
8888

89-
..code-block::html+twig
89+
..configuration-block::
9090

91-
{# app/Resources/views/product/new.html.twig #}
92-
<h1>Adding a new product</h1>
91+
..code-block::html+twig
9392

94-
{{ form_start() }}
95-
{# ... #}
93+
{# app/Resources/views/product/new.html.twig #}
94+
<h1>Adding a new product</h1>
9695

97-
{{ form_row(form.brochure) }}
98-
{{ form_end() }}
96+
{{ form_start(form) }}
97+
{# ... #}
98+
99+
{{ form_row(form.brochure) }}
100+
{{ form_end(form) }}
101+
102+
..code-block::html+php
103+
104+
<!-- app/Resources/views/product/new.html.twig -->
105+
<h1>Adding a new product</h1>
106+
107+
<?php echo $view['form']->start($form) ?>
108+
<?php echo $view['form']->row($form['brochure']) ?>
109+
<?php echo $view['form']->end($form) ?>
99110

100111
Finally, you need to update the code of the controller that handles the form::
101112

@@ -119,7 +130,7 @@ Finally, you need to update the code of the controller that handles the form::
119130
$form = $this->createForm(new ProductType(), $product);
120131
$form->handleRequest($request);
121132

122-
if ($form->isValid()) {
133+
if ($form->isSubmitted() && $form->isValid()) {
123134
// $file stores the uploaded PDF file
124135
/** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
125136
$file = $product->getBrochure();
@@ -128,8 +139,10 @@ Finally, you need to update the code of the controller that handles the form::
128139
$fileName = md5(uniqid()).'.'.$file->guessExtension();
129140

130141
// Move the file to the directory where brochures are stored
131-
$brochuresDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/brochures';
132-
$file->move($brochuresDir, $fileName);
142+
$file->move(
143+
$this->container->getParameter('brochures_directory'),
144+
$fileName
145+
);
133146

134147
// Update the 'brochure' property to store the PDF file name
135148
// instead of its contents
@@ -146,16 +159,27 @@ Finally, you need to update the code of the controller that handles the form::
146159
}
147160
}
148161

162+
Now, create the ``brochures_directory`` parameter that was used in the
163+
controller to specify the directory in which the brochures should be stored:
164+
165+
..code-block::yaml
166+
167+
# app/config/config.yml
168+
169+
# ...
170+
parameters:
171+
brochures_directory:'%kernel.root_dir%/../web/uploads/brochures'
172+
149173
There are some important things to consider in the code of the above controller:
150174

151175
#. When the form is uploaded, the ``brochure`` property contains the whole PDF
152176
file contents. Since this property stores just the file name, you must set
153177
its new value before persisting the changes of the entity;
154178
#. In Symfony applications, uploaded files are objects of the
155-
:class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class, which
179+
:class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class. This class
156180
provides methods for the most common operations when dealing with uploaded files;
157181
#. A well-known security best practice is to never trust the input provided by
158-
users. This also applies to the files uploaded by your visitors. The ``Uploaded``
182+
users. This also applies to the files uploaded by your visitors. The ``UploadedFile``
159183
class provides methods to get the original file extension
160184
(:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getExtension`),
161185
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:
164188
that information. That's why it's always better to generate a unique name and
165189
use the:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension`
166190
method to let Symfony guess the right extension according to the file MIME type;
167-
#. The ``UploadedFile`` class also provides a:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::move`
168-
method to store the file in its intended directory. Defining this directory
169-
path as an application configuration option is considered a good practice that
170-
simplifies the code: ``$this->container->getParameter('brochures_dir')``.
171191

172-
You can now use the following code to link to the PDF brochure of an product:
192+
You can use the following code to link to the PDF brochure of a product:
193+
194+
..configuration-block::
195+
196+
..code-block::html+twig
197+
198+
<a href="{{ asset('uploads/brochures/' ~ product.brochure) }}">View brochure (PDF)</a>
199+
200+
..code-block::html+php
201+
202+
<a href="<?php echo $view['assets']->getUrl('uploads/brochures/'.$product->getBrochure()) ?>">
203+
View brochure (PDF)
204+
</a>
205+
206+
..tip::
207+
208+
When creating a form to edit an already persisted item, the file form type
209+
still expects a:class:`Symfony\\Component\\HttpFoundation\\File\\File`
210+
instance. As the persisted entity now contains only the relative file path,
211+
you first have to concatenate the configured upload path with the stored
212+
filename and create a new ``File`` class::
213+
214+
use Symfony\Component\HttpFoundation\File\File;
215+
// ...
216+
217+
$product->setBrochure(
218+
new File($this->getParameter('brochures_directory').'/'.$product->getBrochure())
219+
);
220+
221+
Creating an Uploader Service
222+
----------------------------
223+
224+
To avoid logic in controllers, making them big, you can extract the upload
225+
logic to a seperate service::
226+
227+
// src/AppBundle/FileUploader.php
228+
namespace AppBundle;
229+
230+
use Symfony\Component\HttpFoundation\File\UploadedFile;
231+
232+
class FileUploader
233+
{
234+
private $targetDir;
235+
236+
public function __construct($targetDir)
237+
{
238+
$this->targetDir = $targetDir;
239+
}
240+
241+
public function upload(UploadedFile $file)
242+
{
243+
$fileName = md5(uniqid()).'.'.$file->guessExtension();
244+
245+
$file->move($this->targetDir, $fileName);
246+
247+
return $fileName;
248+
}
249+
}
250+
251+
Then, define a service for this class:
252+
253+
..configuration-block::
254+
255+
..code-block::yaml
256+
257+
# app/config/services.yml
258+
services:
259+
# ...
260+
app.brochure_uploader:
261+
class:AppBundle\FileUploader
262+
arguments:['%brochures_directory%']
263+
264+
..code-block::xml
265+
266+
<!-- app/config/config.xml-->
267+
<?xml version="1.0" encoding="UTF-8" ?>
268+
<containerxmlns="http://symfony.com/schema/dic/services"
269+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
270+
xsi:schemaLocation="http://symfony.com/schema/dic/services
271+
http://symfony.com/schema/dic/services/services-1.0.xsd"
272+
>
273+
<!-- ...-->
274+
275+
<serviceid="app.brochure_uploader"class="AppBundle\FileUploader">
276+
<argument>%brochures_directory%</argument>
277+
</service>
278+
</container>
279+
280+
..code-block::php
281+
282+
// app/config/services.php
283+
use Symfony\Component\DependencyInjection\Definition;
284+
285+
// ...
286+
$container->setDefinition('app.brochure_uploader', new Definition(
287+
'AppBundle\FileUploader',
288+
array('%brochures_directory%')
289+
));
290+
291+
Now you're ready to use this service in the controller::
292+
293+
// src/AppBundle/Controller/ProductController.php
294+
295+
// ...
296+
public function newAction(Request $request)
297+
{
298+
// ...
299+
300+
if ($form->isValid()) {
301+
$file = $product->getBrochure();
302+
$fileName = $this->get('app.brochure_uploader')->upload($file);
303+
304+
$product->setBrochure($fileName);
173305

174-
..code-block::html+twig
306+
// ...
307+
}
308+
309+
// ...
310+
}
311+
312+
Using a Doctrine Listener
313+
-------------------------
314+
315+
If you are using Doctrine to store the Product entity, you can create a
316+
:doc:`Doctrine listener</cookbook/doctrine/event_listeners_subscribers>` to
317+
automatically upload the file when persisting the entity::
318+
319+
// src/AppBundle/EventListener/BrochureUploadListener.php
320+
namespace AppBundle\EventListener;
321+
322+
use Symfony\Component\HttpFoundation\File\UploadedFile;
323+
use Doctrine\ORM\Event\LifecycleEventArgs;
324+
use Doctrine\ORM\Event\PreUpdateEventArgs;
325+
use AppBundle\Entity\Product;
326+
use AppBundle\FileUploader;
327+
328+
class BrochureUploadListener
329+
{
330+
private $uploader;
331+
332+
public function __construct(FileUploader $uploader)
333+
{
334+
$this->uploader = $uploader;
335+
}
336+
337+
public function prePersist(LifecycleEventArgs $args)
338+
{
339+
$entity = $args->getEntity();
340+
341+
$this->uploadFile($entity);
342+
}
343+
344+
public function preUpdate(PreUpdateEventArgs $args)
345+
{
346+
$entity = $args->getEntity();
347+
348+
$this->uploadFile($entity);
349+
}
350+
351+
private function uploadFile($entity)
352+
{
353+
// upload only works for Product entities
354+
if (!$entity instanceof Product) {
355+
return;
356+
}
357+
358+
$file = $entity->getBrochure();
359+
360+
// only upload new files
361+
if (!$file instanceof UploadedFile) {
362+
return;
363+
}
364+
365+
$fileName = $this->uploader->upload($file);
366+
$entity->setBrochure($fileName);
367+
}
368+
}
369+
370+
Now, register this class as a Doctrine listener:
371+
372+
..configuration-block::
373+
374+
..code-block::yaml
375+
376+
# app/config/services.yml
377+
services:
378+
# ...
379+
app.doctrine_brochure_listener:
380+
class:AppBundle\EventListener\BrochureUploadListener
381+
arguments:['@app.brochure_uploader']
382+
tags:
383+
-{ name: doctrine.event_listener, event: prePersist }
384+
-{ name: doctrine.event_listener, event: preUpdate }
385+
386+
..code-block::xml
387+
388+
<!-- app/config/config.xml-->
389+
<?xml version="1.0" encoding="UTF-8" ?>
390+
<containerxmlns="http://symfony.com/schema/dic/services"
391+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
392+
xsi:schemaLocation="http://symfony.com/schema/dic/services
393+
http://symfony.com/schema/dic/services/services-1.0.xsd"
394+
>
395+
<!-- ...-->
396+
397+
<serviceid="app.doctrine_brochure_listener"
398+
class="AppBundle\EventListener\BrochureUploaderListener"
399+
>
400+
<argumenttype="service"id="app.brochure_uploader"/>
401+
402+
<tagname="doctrine.event_listener"event="prePersist"/>
403+
<tagname="doctrine.event_listener"event="preUpdate"/>
404+
</service>
405+
</container>
406+
407+
..code-block::php
408+
409+
// app/config/services.php
410+
use Symfony\Component\DependencyInjection\Reference;
411+
412+
// ...
413+
$definition = new Definition(
414+
'AppBundle\EventListener\BrochureUploaderListener',
415+
array(new Reference('brochures_directory'))
416+
);
417+
$definition->addTag('doctrine.event_listener', array(
418+
'event' => 'prePersist',
419+
));
420+
$definition->addTag('doctrine.event_listener', array(
421+
'event' => 'preUpdate',
422+
));
423+
$container->setDefinition('app.doctrine_brochure_listener', $definition);
424+
425+
This listeners is now automatically executed when persisting a new Product
426+
entity. This way, you can remove everything related to uploading from the
427+
controller.
428+
429+
..tip::
430+
431+
This listener can also create the ``File`` instance based on the path when
432+
fetching entities from the database::
433+
434+
// ...
435+
use Symfony\Component\HttpFoundation\File\File;
436+
437+
// ...
438+
class BrochureUploadListener
439+
{
440+
// ...
441+
442+
public function postLoad(LifecycleEventArgs $args)
443+
{
444+
$entity = $args->getEntity();
445+
446+
$fileName = $entity->getBrochure();
447+
448+
$entity->setBrochure(new File($this->targetPath.'/'.$fileName));
449+
}
450+
}
175451

176-
<a href="{{ asset('uploads/brochures/' ~ product.brochure) }}">View brochure (PDF)</a>
452+
After adding these lines, configure the listener to also listen for the
453+
``postLoad`` event.
177454

178455
.. _`VichUploaderBundle`:https://github.com/dustin10/VichUploaderBundle

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp