Загрузка файлов
Аватар, обложка статьи, документ — посетители постоянно загружают файлы. В Symfony файл приходит как объект UploadedFile: его проверяют, дают ему новое имя и переносят в папку хранения, а в базе сохраняют только имя.
Поле в сущности
#[ORM\Column(length: 255, nullable: true)]
private ?string $image = null;В базе хранится имя файла, а не сам файл.
Поле в форме
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\File;
$builder->add('imageFile', FileType::class, [
'label' => 'Обложка',
'mapped' => false,
'required' => false,
'constraints' => [
new File(
maxSize: '2M',
extensions: ['jpg', 'png', 'webp'],
extensionsMessage: 'Загрузите картинку JPG, PNG или WebP',
),
],
]);'mapped' => false означает, что поле не связано со свойством сущности: файл обработает контроллер.
Сохранение
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[Route('/blog/new', name: 'blog_new')]
public function new(
Request $request,
EntityManagerInterface $em,
#[Autowire('%kernel.project_dir%/public/uploads')] string $uploadDir,
): Response {
$post = new Post();
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var UploadedFile|null $file */
$file = $form->get('imageFile')->getData();
if ($file) {
$name = bin2hex(random_bytes(16)) . '.' . $file->guessExtension();
$file->move($uploadDir, $name);
$post->setImage($name);
}
$em->persist($post);
$em->flush();
return $this->redirectToRoute('blog_show', ['id' => $post->getId()]);
}
return $this->render('blog/new.html.twig', ['form' => $form]);
}Имя создаётся случайным, а расширение guessExtension() определяет по содержимому файла.
Вывод
{% if post.image %}
<img src="{{ asset('uploads/' ~ post.image) }}" alt="{{ post.title }}">
{% endif %}Атрибут enctype="multipart/form-data" функция form_start() добавит сама.
Методы UploadedFile
| Метод | Что возвращает |
|---|---|
getClientOriginalName() | Имя файла на компьютере посетителя |
guessExtension() | Расширение по содержимому |
getMimeType() | Тип по содержимому |
getSize() | Размер в байтах |
move($dir, $name) | Переносит файл в папку |
Отдельный сервис
Если файлы загружаются в нескольких местах, код сохранения выносят в сервис:
namespace App\Service;
class FileUploader
{
public function __construct(
#[Autowire('%kernel.project_dir%/public/uploads')]
private string $dir,
) {}
public function upload(UploadedFile $file): string
{
$name = bin2hex(random_bytes(16)) . '.' . $file->guessExtension();
$file->move($this->dir, $name);
return $name;
}
}Для больших проектов есть пакет VichUploaderBundle: он связывает файл с сущностью и удаляет его вместе с записью.
Частая ошибка
Нельзя сохранять файл под именем от посетителя: getClientOriginalName() может вернуть shell.php или путь с ../. Файл с расширением .php в общедоступной папке сервер выполнит как программу. Имя всегда создавайте сами, расширение берите из guessExtension(), а список допустимых типов ограничивайте в правилах проверки.