Загрузка и работа с файлами в Symfony - это один из ключевых навыков, который нужно освоить для работы с этим фреймворком.
В этой статье мы с вами рассмотрим основные принципы, как работать с загрузкой файлов.
Конструируем html форму для загрузки файла и ее обработчик
Когда мы говорим о загрузке файла, в первую очередь нам нужно создать html форму, в которую будет загружаться файл и ее обработчик.
Для создания формы мы можем сконструировать ее с помощью Form builder. Вы можете обойтись и без Form Builder, просто этот инструмент упрощает создание html форм и их вывод в Twig шаблоне.
Выглядеть это может примерно вот так:
// src/Form/FileUploadType.php namespace App\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\FileType; class FileUploadType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('file', FileType::class, [ 'label' => 'Upload File (PDF file)', ]); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([]); } }
Кстати, говоря, html-форма для загрузки файла может также и находиться и в шаблоне какого-нибудь фронтенд фреймворка (например, Vue или React), но это уже совсем другой разговор о том, как это можно реализовать и принять загруженный файл.
Далее нам потребуется контроллер и одновременно обработчик, который будет принимать файл отправленный в форме и сохранять его на диск.
// src/Controller/FileUploadController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use App\Form\FileUploadType; use Symfony\Component\HttpFoundation\File\Exception\FileException; class FileUploadController extends AbstractController { /** * @Route("/upload", name="file_upload") */ public function upload(Request $request): Response { $form = $this->createForm(FileUploadType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $file = $form->get('file')->getData(); if ($file) { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $newFilename = $originalFilename.'-'.uniqid().'.'.$file->guessExtension(); try { $file->move( $this->getParameter('uploads_directory'), $newFilename ); } catch (FileException $e) { // Обработка исключений, если файл не может быть загружен } return $this->redirectToRoute('file_upload'); } } return $this->render('upload.html.twig', [ 'form' => $form->createView(), ]); } }
Конструкцией $form = $this->createForm(FileUploadType::class); мы создаем форму, которую в дальнейшем отобразим в twig шаблоне.
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
Эта конструкция позволяет проверить отправлена ли была форма и валидны ли данные, которые из нее отправляются.
Далее мы просто принимаем отправленный файл из запроса и сохраняем в нужную папку на сервере. Это можно сделать с помощью конструкции $file->move
В качестве аргумента для этого метода мы можем указать путь для загрузки файла напрямую, но здесь я решил использовать следующую конструкцию:
$this->getParameter('uploads_directory')
Значение для этого параметра можно указать в
# config/services.yaml parameters: uploads_directory: '%kernel.project_dir%/public/uploads'
В шаблонизаторе Twig форма может выглядеть примерно вот так:
{# templates/upload.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>Загрузка файла</h1> {{ form_start(form) }} {{ form_row(form.file) }} <button class="btn">Загрузить</button> {{ form_end(form) }} {% endblock %}
Данные отправляются на тот же роут, где находится сама форма.
Полный курс по работе с файлами в Symfony здесь:
https://webkyrs.info/category/osnovy-raboty-s-failami-v-symfony-na-primere-zagruzki-izobrazhenii
Сохранение в базе данных
Если мы хотим сохранить файл в базе данных, как правило, сохраняется не сам файл, а ссылка на него.
И здесь есть особенность, сохраняется даже не ссылка, а просто имя файла с его расширением.
Почему нельзя сохранить полный путь?
Если сохраняется полный путь, то мы привязываемся к этому пути и если вы вдруг решите перенести файлы в другой источник, то будут проблемы: нужно будет изменять этот путь для каждой записи в базе данных.
Формирование пути до файла должно происходить в сервисах Symfony, а не в записях в базе данных.
В этом случае наш контроллер может выглядеть примерно следующим образом:
public function upload(Request $request): Response { … if ($form->isSubmitted() && $form->isValid()) { $file = $form->get('file')->getData(); if ($file) { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $newFilename = $originalFilename.'-'.uniqid().'.'.$file->guessExtension(); try { $file->move( $this->getParameter('uploads_directory'), $newFilename ); // Создание новой сущности Product и сохранение имени файла $product = new Product(); $product->setFilename($newFilename); $this->entityManager->persist($product); $this->entityManager->flush(); } catch (FileException $e) { // Обработка исключений, если файл не может быть загружен } return $this->redirectToRoute('file_upload'); } } … }
Бандл liip/imagine-bundle
Для упрощения работы с файлами в Symfony можно дополнительно установить специальный бандл
composer require liip/imagine-bundle
Более подробно про работу с этим бандлом можно посмотреть в этом видеокурсе:
https://webkyrs.info/category/osnovy-raboty-s-failami-v-symfony-na-primere-zagruzki-izobrazhenii
Работа с этим бандлом тоже очень большая тема и не укладывается в рамки этого материала.
Вынос логики загрузки в отдельный сервис
Как правило, логику загрузки файлов выносят в отдельный сервис, чтобы ее можно было переиспользовать.
Например, этот сервис может выглядеть примерно вот так:
// src/Service/FileUploader.php namespace App\Service; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use App\Entity\Product; class FileUploader { private $targetDirectory; private $entityManager; public function __construct(string $targetDirectory, EntityManagerInterface $entityManager) { $this->targetDirectory = $targetDirectory; $this->entityManager = $entityManager; } public function uploadFile(UploadedFile $file): ?Product { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $newFilename = $originalFilename . '-' . uniqid() . '.' . $file->guessExtension(); try { $file->move($this->getTargetDirectory(), $newFilename); // Создание новой сущности Product и сохранение имени файла $product = new Product(); $product->setFilename($newFilename); $this->entityManager->persist($product); $this->entityManager->flush(); return $product; } catch (FileException $e) { // Обработка исключений, если файл не может быть загружен return null; } } public function getTargetDirectory(): string { return $this->targetDirectory; } }
Код контроллера при этом заметно сокращается
/** * @Route("/upload", name="file_upload") */ public function upload(Request $request): Response { $form = $this->createForm(FileUploadType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $file = $form->get('file')->getData(); if ($file) { $product = $this->fileUploader->uploadFile($file); if ($product) { return $this->redirectToRoute('file_upload'); } else { // Обработка ошибки загрузки файла $this->addFlash('error', 'Ошибка загрузки файла.'); } } } return $this->render('upload.html.twig', [ 'form' => $form->createView(), ]); }
Это основы загрузки файлов в Symfony. Здесь есть еще много особенностей и тонкостей, с которыми нам предстоит разобраться в будущем.
Полный курс по работе с файлами в Symfony здесь:
https://webkyrs.info/category/osnovy-raboty-s-failami-v-symfony-na-primere-zagruzki-izobrazhenii
Загружать файлы можно в разные источники и по разному организовать этот процесс, но это уже тема другого разговора. Задача этого материала лишь показать общее представление о загрузке файлов в Symfony.