Загрузка файлов
Файл, отправленный через форму, приходит в запросе отдельно от остальных полей. Slim оборачивает его в объект загруженного файла — у него есть методы, чтобы узнать имя и размер и переместить файл в постоянное место. Добавим к статье обложку.
Форма
Чтобы браузер отправил файл, форме нужен атрибут enctype:
{# templates/posts/cover.twig #}
{% extends 'layout.twig' %}
{% block content %}
<form method="post" action="{{ url_for('posts.cover', {id: post.id}) }}" enctype="multipart/form-data">
{% include '_csrf.twig' %}
<input type="file" name="cover">
<button type="submit">Загрузить</button>
</form>
{% endblock %}Действие, которое показывает эту форму, пишется так же, как NewPostAction.
Куда сохранять
Создайте папку public/uploads. Она лежит внутри public, поэтому сохранённая картинка сразу доступна по адресу /uploads/имя-файла.
Приём файла
// src/Action/UploadCoverAction.php
final class UploadCoverAction
{
public function __invoke(Request $request, Response $response, array $args): Response
{
$file = $request->getUploadedFiles()['cover'] ?? null;
if ($file === null || $file->getError() !== UPLOAD_ERR_OK) {
return $response->withStatus(400);
}
$name = 'post-' . (int) $args['id'] . '.jpg';
$file->moveTo(__DIR__ . '/../../public/uploads/' . $name);
return $response->withHeader('Location', '/posts/' . (int) $args['id'])->withStatus(302);
}
}// config/routes.php
$group->post('/posts/{id:[0-9]+}/cover', UploadCoverAction::class)->setName('posts.cover');Маршрут добавляется в группу с AuthMiddleware. Метод getUploadedFiles() возвращает массив, где ключ — имя поля формы. getError() сообщает, дошёл ли файл целиком, а moveTo() переносит его из временной папки в указанное место. После загрузки файл появится в public/uploads/post-1.jpg.
Методы загруженного файла
| Метод | Что возвращает |
|---|---|
getError() | Код ошибки; UPLOAD_ERR_OK — файл получен |
getSize() | Размер в байтах |
getClientFilename() | Имя файла на компьютере посетителя |
getClientMediaType() | Тип, который сообщил браузер |
moveTo($path) | Перемещает файл |
Проверки
Первый пример принимает что угодно. Перед moveTo() добавьте проверку размера и расширения:
// src/Action/UploadCoverAction.php
$extension = strtolower(pathinfo($file->getClientFilename(), PATHINFO_EXTENSION));
if (!in_array($extension, ['jpg', 'jpeg', 'png', 'webp'], true)) {
return $response->withStatus(422);
}
if ($file->getSize() > 2 * 1024 * 1024) {
return $response->withStatus(422);
}
$name = bin2hex(random_bytes(16)) . '.' . $extension;Разрешены только расширения из списка и файлы не больше 2 МБ. Имя файла приложение придумывает само — случайную строку; её сохраняют в таблице posts, чтобы потом вывести картинку в шаблоне.
Ограничения PHP
Большой файл может не дойти до приложения вовсе: его отсекает сам PHP. За это отвечают настройки upload_max_filesize и post_max_size в php.ini. Если getError() возвращает UPLOAD_ERR_INI_SIZE, файл превысил первое из этих значений.
Частая ошибка
Имя и тип файла присылает браузер посетителя, и подделать их ничего не стоит. Если сохранить файл под исходным именем, злоумышленник загрузит shell.php в папку public и выполнит его, открыв по адресу. Никогда не используйте getClientFilename() как имя на сервере: расширение — только из списка разрешённых, имя — своё.