Управление документами пользователей в PHP: от загрузки до выдачи

Раздел: Разработка на PHP -> Управление пользователями в PHP

Организация работы с документами пользователей в PHP

Основное решение: класс для управления загрузкой и хранением документов

Наиболее эффективный подход предполагает создание выделенного класса UserDocument, который инкапсулирует логику загрузки, валидации, сохранения и удаления файлов. Класс взаимодействует с базой данных через PDO для хранения метаданных (имя, тип, размер, путь, дата загрузки, идентификатор пользователя). Файлы размещаются в файловой системе вне корня веб-сервера (например, /var/data/documents/), что повышает безопасность. Для доступа к документам используется скрипт-посредник, который проверяет права пользователя и отдает файл через readfile().


class UserDocument {
    private PDO $pdo;
    private string $storagePath;
    private int $userId;

    public function __construct(PDO $pdo, string $storagePath, int $userId) {
        $this->pdo = $pdo;
        $this->storagePath = rtrim($storagePath, '/');
        $this->userId = $userId;
    }

    public function upload(array $file, array $allowedMimes = ['application/pdf', 'image/jpeg'], int $maxSize = 5242880): ?int {
        // 1. Валидация
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new RuntimeException('Ошибка загрузки файла: код ' . $file['error']);
        }
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($file['tmp_name']);
        if (!in_array($mime, $allowedMimes, true)) {
            throw new RuntimeException('Недопустимый MIME-тип: ' . $mime);
        }
        if ($file['size'] > $maxSize) {
            throw new RuntimeException('Размер файла превышает лимит ' . ($maxSize / 1024 / 1024) . ' МБ');
        }

        // 2. Генерация уникального имени и перемещение
        $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
        $filename = bin2hex(random_bytes(16)) . '.' . $ext;
        $destination = $this->storagePath . '/' . $filename;
        if (!move_uploaded_file($file['tmp_name'], $destination)) {
            throw new RuntimeException('Не удалось переместить файл');
        }

        // 3. Сохранение метаданных в БД
        $sql = 'INSERT INTO user_documents (user_id, original_name, filename, mime_type, size, storage_path) VALUES (?, ?, ?, ?, ?, ?)';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$this->userId, $file['name'], $filename, $mime, $file['size'], $destination]);
        return (int) $this->pdo->lastInsertId();
    }
}

User type php name (тип пользователя в php)

В этом примере класс принимает PDO-соединение, путь к хранилищу и идентификатор пользователя. Метод upload() выполняет проверки и перемещает файл. Возвращается идентификатор вставленной записи. Аналогично реализуются методы delete(), getById() и serve() для выдачи файла.

Типичные проблемы и их исправление

  • Ошибка 500 при загрузке – проверка upload_max_filesize и post_max_size в php.ini, а также max_file_uploads.
  • Файл не сохраняется – неверно задан путь $storagePath или отсутствуют права на запись для веб-сервера.
  • Подмена MIME-типа – проверка через finfo (по содержимому) вместо $_FILES['file']['type'] (который приходит от клиента).
  • SQL-инъекция – обязательное использование подготовленных запросов PDO.

Вариант 1: Как загрузить документ с валидацией только по расширению?

Иногда требуется просто проверить расширение файла, без анализа содержимого. Такой подход удобен для быстрых прототипов, но не гарантирует безопасность – злоумышленник может переименовать вредоносный скрипт в .pdf. Пример:


$allowedExtensions = ['pdf', 'jpg', 'png'];
$ext = strtolower(pathinfo($_FILES['doc']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExtensions, true)) {
    die('Расширение не разрешено');
}

User group php (группа пользователей в php)

Этот вариант подходит для внутренних систем с доверенными пользователями, где риски минимальны. Однако в публичных приложениях лучше применять проверку MIME через finfo.

Вариант 2: Как хранить документы непосредственно в базе данных (BLOB)?

Хранение файлов целиком в столбце BLOB (например, LONGBLOB) удобно для небольших документов (фотографии, подписи) и упрощает резервное копирование – данные находятся в одной БД. Пример вставки:


$sql = 'INSERT INTO user_documents (user_id, original_name, content, mime_type) VALUES (?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
    $userId,
    $_FILES['doc']['name'],
    file_get_contents($_FILES['doc']['tmp_name']),
    mime_content_type($_FILES['doc']['tmp_name'])
]);

Php user ip (ip-адрес пользователя в php)

Недостатки: БД быстро растёт в размере, замедляется резервное копирование, сложно отдавать файлы через веб-сервер напрямую (требуется PHP-скрипт). Используется только для файлов до нескольких мегабайт.

Вариант 3: Как реализовать множественную загрузку через одно поле формы?

Для поля с атрибутом multiple в HTML массив $_FILES имеет структуру с вложенными массивами. Пример обработки:


if (isset($_FILES['documents'])) {
    $files = $_FILES['documents'];
    $count = count($files['name']);
    for ($i = 0; $i < $count; $i++) {
        $singleFile = [
            'name'     => $files['name'][$i],
            'type'     => $files['type'][$i],
            'tmp_name' => $files['tmp_name'][$i],
            'error'    => $files['error'][$i],
            'size'     => $files['size'][$i],
        ];
        // then process $singleFile with the class method
    }
}

Remote user php (удаленный пользователь в php)

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

Вариант 4: Как защитить загруженные документы от прямого доступа по URL?

Файлы следует сохранять вне DocumentRoot (например, /var/data/documents/). Для выдачи документа использовать PHP-скрипт, который проверяет права пользователя и отдаёт файл:


$document = UserDocument::getById($id, $pdo);
if ($document && $document['user_id'] === $currentUserId) {
    header('Content-Type: ' . $document['mime_type']);
    header('Content-Disposition: inline; filename="' . $document['original_name'] . '"');
    readfile($document['storage_path']);
    exit;
} else {
    http_response_code(403);
    echo 'Доступ запрещён';
}

Этот подход гарантирует, что только авторизованный владелец документа сможет его просмотреть или скачать.

Ошибки при работе с разными вариантами

  • BLOB-поле слишком маленькое – для файлов больше 1 МБ выбирать MEDIUMBLOB или LONGBLOB.
  • Проблемы с кодировкой имени файла – использовать basename() и mb_convert_encoding() при необходимости.
  • Утечка пути к файлу – никогда не передавать storage_path клиенту, только идентификатор записи.
- Php script user (скрипт пользователя в php)
- Name php id user (имя пользователя по id в php)
- Php get id user (получение id пользователя в php)

Расширенные примеры работы с документами пользователя

Пример 1: Полный класс UserDocument с методами upload, delete, serve

Реализация включает все типичные операции. Обратите внимание на обработку ошибок и логирование.

Пример

class UserDocument {
    private PDO $pdo;
    private string $storagePath;
    private int $userId;

    public function __construct(PDO $pdo, string $storagePath, int $userId) {
        $this->pdo = $pdo;
        $this->storagePath = rtrim($storagePath, '/');
        $this->userId = $userId;
    }

    public function upload(array $file, array $allowedMimes = ['application/pdf'], int $maxSize = 10485760): ?int {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new RuntimeException('Upload error code: ' . $file['error']);
        }
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($file['tmp_name']);
        if (!in_array($mime, $allowedMimes, true)) {
            throw new UnexpectedValueException('MIME type ' . $mime . ' not allowed');
        }
        if ($file['size'] > $maxSize) {
            throw new RangeException('File size ' . $file['size'] . ' exceeds limit ' . $maxSize);
        }
        $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
        $newName = bin2hex(random_bytes(16)) . '.' . $ext;
        $dest = $this->storagePath . '/' . $newName;
        if (!move_uploaded_file($file['tmp_name'], $dest)) {
            throw new RuntimeException('Failed to move uploaded file');
        }
        $sql = 'INSERT INTO user_documents (user_id, original_name, filename, mime_type, size, storage_path, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$this->userId, $file['name'], $newName, $mime, $file['size'], $dest]);
        return (int) $this->pdo->lastInsertId();
    }

    public function delete(int $documentId): bool {
        $stmt = $this->pdo->prepare('SELECT storage_path FROM user_documents WHERE id = ? AND user_id = ?');
        $stmt->execute([$documentId, $this->userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            return false;
        }
        if (file_exists($row['storage_path'])) {
            unlink($row['storage_path']);
        }
        $stmt = $this->pdo->prepare('DELETE FROM user_documents WHERE id = ?');
        return $stmt->execute([$documentId]);
    }

    public function serve(int $documentId): void {
        $stmt = $this->pdo->prepare('SELECT * FROM user_documents WHERE id = ? AND user_id = ?');
        $stmt->execute([$documentId, $this->userId]);
        $doc = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$doc) {
            http_response_code(404);
            echo 'Document not found';
            return;
        }
        header('Content-Type: ' . $doc['mime_type']);
        header('Content-Disposition: filename="' . basename($doc['original_name']) . '"');
        header('Content-Length: ' . $doc['size']);
        readfile($doc['storage_path']);
    }

    // Additional methods: getList(), getById() etc.
}

Результат работы метода upload при успешной загрузке (например, через отладку):

int(42) // идентификатор записи о документе в таблице user_documents

Пример 2: Работа с изображениями – автоматическое создание миниатюры

Используя библиотеку GD или Intervention Image, можно после загрузки создавать уменьшенную копию документа (например, для аватарок).

Пример

// после успешной загрузки файла
$image = new \Intervention\Image\ImageManager(/* driver */);
$img = $image->make($destination);
$thumbnailPath = dirname($destination) . '/thumb_' . $newName;
$img->resize(150, 150)->save($thumbnailPath);

// сохраняем путь к оригиналу и миниатюре в БД
$sql = 'INSERT INTO user_documents (user_id, original_name, filename, mime_type, size, storage_path, thumbnail_path) VALUES (?, ?, ?, ?, ?, ?, ?)';

После этого миниатюра может быть показана в списке документов без загрузки полного файла.

Пример 3: Загрузка документов из base64 (API, мобильные клиенты)

Иногда документы приходят в формате base64. Обработка такого варианта:

Пример

$base64 = 'data:application/pdf;base64,JVBERi0xLjQK...';
$data = explode(',', $base64)[1];
$decoded = base64_decode($data, true);
if ($decoded === false) {
    throw new InvalidArgumentException('Invalid base64 data');
}
$tmpPath = sys_get_temp_dir() . '/' . uniqid('upload_', true) . '.tmp';
file_put_contents($tmpPath, $decoded);
$file = [
    'name'     => 'document_from_api.pdf',
    'type'     => 'application/pdf',
    'tmp_name' => $tmpPath,
    'error'    => UPLOAD_ERR_OK,
    'size'     => strlen($decoded),
];
$docId = $uploader->upload($file);
unlink($tmpPath); // удаляем временный файл

Такой код полезен при интеграции с внешними сервисами или для загрузки через REST API.

Пример 4: Вывод списка документов пользователя с ссылками на скачивание

Пример

$stmt = $pdo->prepare('SELECT id, original_name, mime_type, size, created_at FROM user_documents WHERE user_id = ?');
$stmt->execute([$userId]);
$documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<ul>
<? foreach ($documents as $doc): ?>
    <li>
        <a href="/download.php?id=<?= $doc['id'] ?>"><?= htmlspecialchars($doc['original_name']) ?></a>
        (<?= number_format($doc['size'] / 1024, 2) ?> КБ)
    </li>
<? endforeach; ?>
</ul>

В файле download.php вызывается метод serve() соответствующего класса.

Документ пользователя в PHP - comments

En
Document php user (php)