Реализация модуля управления документами в PHP
Модуль документов в PHP: подходы и реализация
Модуль управления документами в PHP охватывает загрузку, хранение, обработку и выдачу файлов. Выбор архитектуры зависит от масштаба проекта: от простого файлового хранилища до интеграции с базами данных и облачными сервисами. Ниже рассмотрено основное эффективное решение и альтернативные варианты.
Основное решение: гибрид файловой системы и базы данных
Сочетание хранения файлов на диске и метаданных в таблице БД обеспечивает баланс производительности и управляемости. Файлы сохраняются в структурированном каталоге (например, /uploads/год/месяц/), а в базе записывается путь, имя, размер, тип, дата и владелец.
Реализация загрузки
// Загрузка файла с сохранением метаданных
$uploadDir = __DIR__ . '/uploads/' . date('Y/m');
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$fileName = uniqid() . '_' . basename($_FILES['document']['name']);
$filePath = $uploadDir . '/' . $fileName;
if (move_uploaded_file($_FILES['document']['tmp_name'], $filePath)) {
$stmt = $pdo->prepare('INSERT INTO documents (filename, filepath, filesize, mime_type, created_at) VALUES (?, ?, ?, ?, NOW())');
$stmt->execute([
$_FILES['document']['name'],
$filePath,
$_FILES['document']['size'],
mime_content_type($filePath)
]);
echo 'Document uploaded successfully.';
}
Document php module (модуль документов в php)
Выдача файла пользователю
// Скачивание документа по ID
$id = (int)$_GET['id'];
$stmt = $pdo->prepare('SELECT * FROM documents WHERE id = ?');
$stmt->execute([$id]);
$doc = $stmt->fetch();
if ($doc && file_exists($doc['filepath'])) {
header('Content-Type: ' . $doc['mime_type']);
header('Content-Disposition: attachment; filename="' . $doc['filename'] . '"');
header('Content-Length: ' . $doc['filesize']);
readfile($doc['filepath']);
exit;
}
Типичные ошибки и их решения:
- Ошибка прав доступа на каталог uploads. Решение: установить chmod 0755 или 0777 для каталога и убедиться, что веб-сервер является владельцем.
- Загрузка файлов большого размера обрывается. Решение: увеличить лимиты upload_max_filesize и post_max_size в php.ini.
- Конфликт имён файлов. Решение: использовать уникальный префикс (uniqid, md5 времени и т.п.).
Цель: обеспечить удобное администрирование, возможность поиска по метаданным, защиту от дублирования имён и быстрый доступ к файлам.
Вариант 1: Простая загрузка без базы данных
Вопрос: Как организовать загрузку документов, когда требуется только временное хранение или небольшой проект?
Файлы сохраняются в каталог, информация о них хранится в сессии или не хранится вовсе. Пример:
$targetDir = 'uploads/';
$targetFile = $targetDir . basename($_FILES['file']['name']);
move_uploaded_file($_FILES['file']['tmp_name'], $targetFile);
echo 'File saved: ' . $targetFile;
Проблемы: дублирование имён, отсутствие контроля версий, невозможность поиска. Решается только для одноразовых задач.
Вариант 2: Хранение файлов в базе данных (BLOB)
Вопрос: Когда необходимо обеспечить бэкап и переносимость данных вместе с файлами?
Файлы сохраняются в BLOB-поле таблицы. Это удобно для небольших файлов (до нескольких мегабайт), но серьёзно нагружает БД.
// Сохранение в BLOB
$data = file_get_contents($_FILES['file']['tmp_name']);
$stmt = $pdo->prepare('INSERT INTO documents_blob (filename, filedata, mime_type) VALUES (?, ?, ?)');
$stmt->execute([$_FILES['file']['name'], $data, $_FILES['file']['type']]);
// Выдача
$stmt = $pdo->prepare('SELECT * FROM documents_blob WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
header('Content-Type: ' . $row['mime_type']);
echo $row['filedata'];
Проблемы: снижение производительности при росте размера файлов, сложность фрагментации БД, невозможность прямого доступа через веб-сервер. Рекомендуется только для файлов до 1-2 МБ.
Вариант 3: Использование сторонних библиотек для генерации документов
Вопрос: Как программно создавать PDF, Excel или Word документы?
Библиотеки TCPDF (PDF), PhpSpreadsheet (Excel) и PhpWord (Word) позволяют генерировать документы с данными из БД.
// Пример с TCPDF
require_once 'tcpdf/tcpdf.php';
$pdf = new TCPDF();
$pdf->AddPage();
$pdf->SetFont('dejavusans', '', 14);
$pdf->Write(0, 'Привет, мир документов!');
$pdf->Output('generated.pdf', 'D');
Проблемы: необходимость установки библиотек через Composer, потребление памяти при больших объёмах данных. Решение: генерировать частями или использовать потоковую запись.
Вариант 4: Интеграция с облачным хранилищем (Amazon S3, Google Drive)
Вопрос: Как масштабировать хранение документов для высоконагруженного проекта?
Использование API облачных сервисов снимает нагрузку с собственного сервера. Пример для AWS S3 через SDK:
use Aws\S3\S3Client;
$s3 = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
'credentials' => [
'key' => 'YOUR_KEY',
'secret' => 'YOUR_SECRET'
]
]);
$result = $s3->putObject([
'Bucket' => 'my-bucket',
'Key' => 'documents/' . $_FILES['file']['name'],
'Body' => fopen($_FILES['file']['tmp_name'], 'rb'),
'ACL' => 'private'
]);
Проблемы: зависимость от внешнего провайдера, стоимость хранения и передачи, необходимость управления ключами доступа. Решение: использовать кэширование и подписанные URL для временного доступа.
Расширенные примеры работы с документами
1. Генерация многостраничного PDF с таблицей
require_once 'tcpdf/tcpdf.php';
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->SetCreator('MyApp');
$pdf->AddPage();
// Заголовок
$pdf->SetFont('dejavusans', 'B', 16);
$pdf->Cell(0, 10, 'Отчет по документам', 0, 1, 'C');
// Таблица
$pdf->SetFont('dejavusans', '', 10);
$header = array('ID', 'Имя', 'Дата');
$data = array(
array(1, 'Договор.pdf', '2024-01-15'),
array(2, 'Счёт.xlsx', '2024-02-20'),
);
$w = array(20, 100, 70);
for($i = 0; $i < count($header); $i++) {
$pdf->Cell($w[$i], 7, $header[$i], 1, 0, 'C');
}
$pdf->Ln();
foreach ($data as $row) {
foreach ($row as $j => $cell) {
$pdf->Cell($w[$j], 6, $cell, 1);
}
$pdf->Ln();
}
$pdf->Output('report.pdf', 'I');
Результат: PDF-документ с таблицей, отображаемый в браузере (inline).
2. Импорт данных из Excel с помощью PhpSpreadsheet
use PhpOffice\PhpSpreadsheet\IOFactory;
$inputFileName = 'uploaded_contracts.xlsx';
$spreadsheet = IOFactory::load($inputFileName);
$worksheet = $spreadsheet->getActiveSheet();
$highestRow = $worksheet->getHighestRow();
$stmt = $pdo->prepare('INSERT INTO imported_docs (contract_id, client, amount) VALUES (?, ?, ?)');
for ($row = 2; $row <= $highestRow; $row++) { // пропускаем заголовок
$contractId = $worksheet->getCell('A' . $row)->getValue();
$client = $worksheet->getCell('B' . $row)->getValue();
$amount = $worksheet->getCell('C' . $row)->getValue();
$stmt->execute([$contractId, $client, $amount]);
}
echo 'Импортировано записей: ' . ($highestRow - 1);
Результат: данные из Excel перенесены в базу данных.
3. Многопоточная загрузка с проверкой безопасности
// Фильтрация типа файла и проверка на вирусы через ClamAV
$allowedMime = ['application/pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
if (!in_array($mime, $allowedMime)) {
die('Недопустимый тип файла.');
}
// Дополнительная проверка через ClamAV (требуется расширение php-clamav)
if (function_exists('clamav_scan_file')) {
$result = clamav_scan_file($_FILES['file']['tmp_name']);
if ($result !== false) {
die('Обнаружен вирус.');
}
}
// Далее стандартное сохранение
Результат: загружаются только безопасные файлы разрешённых форматов.
4. Создание архива ZIP из нескольких документов
$zip = new ZipArchive();
$zipName = 'export_' . time() . '.zip';
if ($zip->open($zipName, ZipArchive::CREATE) === true) {
// Добавляем файлы из БД
$stmt = $pdo->query('SELECT filepath, filename FROM documents WHERE id IN (1,2,3)');
while ($doc = $stmt->fetch()) {
$zip->addFile($doc['filepath'], $doc['filename']);
}
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $zipName . '"');
readfile($zipName);
unlink($zipName);
exit;
}
Результат: пользователь скачивает архив ZIP, содержащий выбранные файлы.
5. Обработка изображений (поворот, ресайз) с помощью GD
$source = imagecreatefromjpeg('document_image.jpg');
// Поворот на 90 градусов
$rotated = imagerotate($source, 90, 0);
// Изменение размера до 800x600
$resized = imagescale($rotated, 800, 600);
imagejpeg($resized, 'processed_image.jpg', 90);
imagedestroy($source);
imagedestroy($rotated);
imagedestroy($resized);
Результат: файл processed_image.jpg с поворотом и новым размером.