Загрузка и работа с файлами в 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.