PHP: Разработка микрофреймворка

Теория: Файлы

Загрузка файлов в PHP

В PHP для загрузки файлов не нужны внешние библиотеки. Достаточно правильно настроить форму и обработчик.

Что нужно в HTML-форме

Чтобы браузер отправлял файл, в форме должны быть:

  • enctype="multipart/form-data".
  • Поле <input type="file">.

Без multipart/form-data файл на сервер не придет как бинарные данные.

<form enctype="multipart/form-data" action="/users" method="post">
  <div>
    Name
    <input type="text" name="user[name]" value="<?= $user['name'] ?>">
  </div>

  <div>
    Avatar
    <input type="file" name="user[avatar]">
    <?php if (isset($errors['avatar'])) : ?>
      <div style="color: red"><?= $errors['avatar'] ?></div>
    <?php endif ?>
  </div>

  <input type="submit">
</form>

Где лежат загруженные файлы

После отправки данные о файле попадают в $_FILES. Важно понимать, что это не сам файл, а метаданные:

  • Исходное имя.
  • MIME-тип.
  • Временный путь на сервере.
  • Код ошибки.
  • Размер.

Файл сначала сохраняется во временную директорию. Потом его нужно явно переместить в постоянное место хранения.

Почему структура $_FILES неудобная

У $_FILES специфичная структура: на верхнем уровне идут ключи name, type, tmp_name, error, size, а уже внутри - имена полей формы.

Из-за этого в реальных проектах часто используют обертки фреймворка, где работа с файлами дается через более понятный интерфейс.

Базовая схема обработки

Типичная последовательность в обработчике такая:

  1. Проверить, что поле файла действительно пришло.
  2. Прочитать код ошибки из $_FILES.
  3. Обработать сценарий UPLOAD_ERR_NO_FILE, если файл необязательный.
  4. При UPLOAD_ERR_OK переместить файл из временной директории.
  5. При другой ошибке показать пользователю понятное сообщение и записать детали в лог.

Код ошибки нельзя показывать пользователю как есть. Лучше сопоставлять его с читаемым текстом.

<?php

function codeToMessage(int $code): string
{
    return match ($code) {
        UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
        UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
        UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded',
        UPLOAD_ERR_NO_FILE => 'No file was uploaded',
        UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
        UPLOAD_ERR_EXTENSION => 'File upload stopped by extension',
        default => 'Unknown upload error',
    };
}

Перемещение файла

Для сохранения используйте move_uploaded_file($tmpPath, $newPath).

Эта функция дополнительно проверяет, что файл действительно был загружен через HTTP POST. Это защита от части атак при подмене пути.

Если move_uploaded_file() вернула false, это серверная проблема, которую обязательно нужно логировать.

<?php

$app->post('/users', function (array $meta, array $params, array $attributes) use ($repository) {
    $user = $params['user'];
    $errors = [];

    if (array_key_exists('user', $_FILES)) {
        $key = 'avatar';
        $errorCode = $_FILES['user']['error'][$key];

        if ($errorCode !== UPLOAD_ERR_NO_FILE) {
            if ($errorCode !== UPLOAD_ERR_OK) {
                $errors['avatar'] = codeToMessage($errorCode);
            } else {
                $tmpName = $_FILES['user']['tmp_name'][$key];
                $name = $_FILES['user']['name'][$key];
                $newName = 'images' . DIRECTORY_SEPARATOR . $name;

                if (!move_uploaded_file($tmpName, $newName)) {
                    $errors['avatar'] = 'Something was wrong';
                } else {
                    $user['avatar'] = $name;
                }
            }
        }
    }

    if (empty($errors)) {
        $repository->insert($user);
        return response()->redirect('/');
    }

    return response(render('users/new', ['user' => $user, 'errors' => $errors]))
        ->withStatus(422);
});

Именование файлов и безопасность

Не сохраняйте файл под исходным именем пользователя.

Риски:

  • Возможны пробелы, Unicode-символы и другие проблемные символы.
  • Возможны совпадения имен и перезапись чужого файла.
  • Возможны проблемы валидации и безопасности.

Надежнее использовать:

  • Уникальное имя (например, через хеш или UUID).
  • Путь, связанный с идентификатором записи в БД.

Оригинальное имя при необходимости храните отдельным полем в базе.

Согласованность с базой данных

Если вы пишете запись в БД и файл на диск, важно не получить частичное сохранение.

Пример плохого сценария:

  • Запись в БД добавлена.
  • Сохранение файла завершилось ошибкой.

В результате в базе остается ссылка на несуществующий файл. Для надежной схемы используют транзакции и фиксируют изменения только после успешного переноса файла.

Итог

При работе с загрузкой файлов достаточно помнить базовый набор правил:

  • Отправлять форму с multipart/form-data.
  • Читать метаданные файла из $_FILES.
  • Проверять код загрузки и обрабатывать ошибки.
  • Сохранять файл через move_uploaded_file().
  • Не использовать исходное имя файла как итоговый путь.

Рекомендуемые программы

Дальше

Завершено

0 / 11

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845