<MyRusakov.ru />

WordPress 6 с Нуля до Гуру

WordPress 6 с Нуля до Гуру

Этот курс научит Вас созданию самых разных сайтов на самой популярной в мире CMS - WordPress. Вы увидите установку и настройку локального сервера, разбор каждой настройки, каждой кнопки и каждого пункта меню в панели WordPress.

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

Помимо уроков к курсу идут упражнения для закрепления материала.

И, наконец, к курсу идёт ценнейший Бонус по тому, как используя ChatGPT и создавая контент для сайта, можно выйти на пассивный доход. Вы наглядно увидите, как зарегистрироваться в ChatGPT (в том числе, и если Вы из России), как правильно выбрать тему для сайта, как правильно генерировать статьи для него(чтобы они индексировались поисковыми системами) и как правильно монетизировать трафик на сайте.

Подробнее
Подписка

Подпишитесь на мой канал на YouTube, где я регулярно публикую новые видео.

YouTube Подписаться

Подписавшись по E-mail, Вы будете получать уведомления о новых статьях.

Подписка Подписаться

Добавляйтесь ко мне в друзья ВКонтакте! Отзывы о сайте и обо мне оставляйте в моей группе.

Мой аккаунт Мой аккаунт Моя группа
Опрос

Какая тема Вас интересует больше?

Загрузка файлов в PHP: пошаговое руководство с безопасностью и примерами

Загрузка файлов в PHP: пошаговое руководство с безопасностью и примерами

Запрос «загрузка файлов в PHP» чаще всего приводит к примерам на пару строк, но в продакшене так делать нельзя. Ниже — пошаговое руководство с акцентом на безопасность: форма, приём через $_FILES, валидация MIME, ограничение размеров, безопасное хранилище, множественная загрузка и частые ошибки.

1) HTML‑форма для загрузки файла

Обязательные атрибуты: method="post" и enctype="multipart/form-data". Добавим CSRF‑токен и ограничитель размера на клиенте (не полагайтесь на него полностью).

<?php
session_start();
if (empty($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}
?>

<form action="upload.php" method="post" enctype="multipart/form-data">
  <input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf'], ENT_QUOTES) ?>">
  <!-- Ограничитель для браузера (сервер всё равно проверяет сам) -->
  <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- 2 МБ -->
  <input type="file" name="file" accept="image/png,image/jpeg,image/webp,application/pdf" required>
  <button type="submit">Загрузить</button>
</form>

2) Базовая обработка на сервере (безопасно)

Ключевые идеи: не доверять расширению и $_FILES['type'], проверять MIME через finfo_file, задавать лимиты размера, переименовывать файл случайным именем и хранить вне web‑корня.

<?php
declare(strict_types=1);
session_start();

if (!isset($_POST['csrf'], $_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $_POST['csrf'])) {
    http_response_code(403);
    exit('Неверный CSRF-токен');
}

// 1) Проверяем наличие файла и ошибки PHP
if (!isset($_FILES['file'])) {
    exit('Файл не получен');
}

$err = $_FILES['file']['error'];
if ($err !== UPLOAD_ERR_OK) {
    $map = [
        UPLOAD_ERR_INI_SIZE   => 'Размер превысил upload_max_filesize',
        UPLOAD_ERR_FORM_SIZE  => 'Размер превысил MAX_FILE_SIZE',
        UPLOAD_ERR_PARTIAL    => 'Файл загружен частично',
        UPLOAD_ERR_NO_FILE    => 'Файл не выбран',
        UPLOAD_ERR_NO_TMP_DIR => 'Нет временной директории',
        UPLOAD_ERR_CANT_WRITE => 'Ошибка записи на диск',
        UPLOAD_ERR_EXTENSION  => 'PHP-расширение остановило загрузку',
    ];
    exit($map[$err] ?? 'Неизвестная ошибка загрузки');
}

// 2) Серверные лимиты размера
$maxSize = 2 * 1024 * 1024; // 2 МБ
if ($_FILES['file']['size'] > $maxSize) {
    exit('Слишком большой файл (макс. 2 МБ)');
}

// 3) Разрешённые типы и расширения
$allowed = [
    'png' => 'image/png',
    'jpg' => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'webp' => 'image/webp',
    'pdf' => 'application/pdf',
];

// Исходное имя используется только для получения расширения
$originalName = $_FILES['file']['name'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!array_key_exists($ext, $allowed)) {
    exit('Недопустимое расширение файла');
}

// 4) Проверка реального MIME с помощью Fileinfo
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
if ($mime !== $allowed[$ext]) {
    exit('Несоответствие MIME-типа');
}

// Доп. проверка для изображений (защита от подмены)
if (str_starts_with($mime, 'image/')) {
    $imgSize = @getimagesize($_FILES['file']['tmp_name']);
    if ($imgSize === false) {
        exit('Файл не является валидным изображением');
    }
}

// 5) Хранилище вне web-корня
$storage = dirname(__DIR__) . '/storage/uploads'; // ../storage/uploads
if (!is_dir($storage)) {
    if (!mkdir($storage, 0775, true) && !is_dir($storage)) {
        exit('Не удалось создать директорию для загрузок');
    }
}

// 6) Случайное имя файла
$basename = bin2hex(random_bytes(16));
$target = $storage . '/' . $basename . '.' . $ext;

// 7) Безопасное перемещение
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
    exit('Источник файла не является загруженным через HTTP');
}

if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
    exit('Не удалось сохранить файл');
}

chmod($target, 0644); // минимально необходимые права

echo 'Файл успешно загружен! Идентификатор: ' . $basename;

Почему так безопаснее

  • Хранение вне web‑корня исключает прямой HTTP‑доступ к загруженным файлам, даже если это скрипты.
  • Проверка finfo_file защищает от подмены расширения и заголовков.
  • Случайные имена без исходного названия предотвращают коллизии и утечку информации о пользователях.
  • Жёсткие лимиты размера и типов уменьшают риск DoS и загрузки вредоносных файлов.

3) Множественная загрузка файлов

Во входных данных имена будут массивами. Логику проверки вынесем в функцию и применим в цикле.

<!-- форма -->
<form action="multi-upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="files[]" multiple accept="image/png,image/jpeg">
  <button>Загрузить</button>
</form>
<?php
function process_upload(array $file): string {
    // Здесь можно переиспользовать логику из предыдущего примера
    // ... проверки ошибок, MIME, размера и т.д.
    // Вернём путь или ID сохранённого файла
    return 'ok';
}

$files = $_FILES['files'] ?? null;
if (!$files) exit('Нет файлов');

$results = [];
for ($i = 0; $i < count($files['name']); $i++) {
    $file = [
        'name'     => $files['name'][$i],
        'type'     => $files['type'][$i],
        'tmp_name' => $files['tmp_name'][$i],
        'error'    => $files['error'][$i],
        'size'     => $files['size'][$i],
    ];
    $results[] = process_upload($file);
}
print_r($results);

4) Настройка php.ini и сервера

  • upload_max_filesize — максимальный размер одного файла (например, 5M).
  • post_max_size — должен быть больше или равен upload_max_filesize (например, 6M).
  • max_file_uploads — лимит количества файлов за один запрос (по умолчанию 20).
  • file_uploads = On — разрешить загрузку файлов.
  • max_input_time, memory_limit — косвенно влияют на большие загрузки.
  • Nginx: client_max_body_size; Apache: LimitRequestBody — не забудьте согласовать с php.ini.

5) Как выдавать загруженные файлы безопасно

Если файлы лежат вне web‑корня, отдавайте их через контроллер, явно задавая заголовки и тип, без выполнения кода внутри файла.

<?php
// download.php?id=...
$id = preg_replace('/[^a-f0-9]/', '', $_GET['id'] ?? '');
$path = dirname(__DIR__) . '/storage/uploads/' . $id . '.pdf'; // например, для PDF
if (!is_file($path)) {
    http_response_code(404);
    exit('Файл не найден');
}
header('Content-Type: application/pdf');
header('Content-Length: ' . filesize($path));
header('Content-Disposition: inline; filename="document.pdf"');
readfile($path);

Храните метаданные в БД: формат, оригинальное имя, путь, размер, владелец, права доступа. Так вы сможете точно контролировать, что и кому отдаёте.

6) Частые ошибки и как их избежать

  • Доверие к $_FILES['type'] — используйте finfo_file и доп. проверки изображений.
  • Сохранение в доступной директории: поместите файлы вне web‑корня или отключайте исполнение скриптов там, где храните загрузки.
  • Отсутствие переименования — коллизии, утечки путей и XSS через имя файла.
  • Нет серверных лимитов размера — легко получить переполнение или DoS.
  • Пропуск проверки ошибок PHP — файл может быть частично загружен.
  • Игнорирование CSRF — злоумышленник может инициировать нежелательную загрузку от имени пользователя.

7) Мини‑чек‑лист безопасной загрузки

  • Форма: POST + multipart, CSRF‑токен, разумный accept и клиентский лимит.
  • Сервер: проверка кодов ошибок, размер, MIME через Fileinfo, доп. проверка изображений.
  • Whitelist расширений и типов, случайные имена, права 0644.
  • Хранение вне web‑корня, выдача через контроллер с корректными заголовками.
  • Согласованные лимиты php.ini и веб‑сервера, обработка множественной загрузки.

Что дальше изучать

Для системного закрепления темы — работа с БД, авторизацией, MVC‑структурой, тестами и деплоем — рекомендую пройти практический курс: Пройти интенсивный курс «PHP и MySQL с Нуля до Гуру 3.0» и собрать проекты с нуля.

Теперь вы знаете не только как сделать «загрузчик на коленке», но и как обеспечить безопасность и предсказуемость этого процесса в реальном проекте. Удачной разработки!

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (https://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: https://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: https://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как создать свой сайт

  2. Текстовая ссылка:

    Она выглядит вот так: Как создать свой сайт

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Комментарии (0):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.