Генерация PDF и Excel в PHP: как выбрать библиотеку и не влипнуть
Отчёт на 50 тысяч строк кладёт PHP по памяти, а PDF с кириллицей выдаёт квадраты. Разбираем, чем генерировать документы в PHP 8.x и какой ценой платит каждый подход.

Задача выглядит простой: сформировать счёт в PDF или выгрузить отчёт в Excel. Проблемы начинаются на проде. HTML-в-PDF рендерит счёт за 400 мс на строку и валит генерацию накладной из 200 позиций в таймаут. Выгрузка Excel через PhpSpreadsheet на 50 000 строк съедает 1,5 ГБ памяти и падает с Allowed memory size exhausted. А кириллица в PDF превращается в квадраты, потому что в дефолтном шрифте её просто нет.
Ниже — как выбрать инструмент под конкретную задачу в PHP 8.1–8.3, где каждый подход ломается и чем за него платят. Кода будет ровно столько, чтобы показать разницу между подходами, а не переписать документацию.
PDF: три способа и за что платит каждый
Способов генерировать PDF в PHP три, и они принципиально разные по природе. Один рисует документ командами, второй рендерит HTML, третий отдаёт вёрстку внешнему движку.
Прямая отрисовка: FPDF и TCPDF
FPDF и TCPDF строят документ императивно: вы задаёте координаты, ширину ячейки, шрифт и пишете текст. Никакого HTML и CSS в основе — вы управляете каждым миллиметром. Это быстро и предсказуемо по памяти, но верстать сложную таблицу руками мучительно.
Главная ловушка новичка — кириллица. FPDF из коробки работает с однобайтовыми кодировками. Чтобы вывести русский текст, нужен шрифт с поддержкой Unicode и правильная кодировка. TCPDF умеет UTF-8 сразу, но крупнее и медленнее.
<?php
// TCPDF 6.x, PHP 8.2
require 'vendor/autoload.php';
$pdf = new \TCPDF('P', 'mm', 'A4', true, 'UTF-8');
$pdf->SetPrintHeader(false);
$pdf->SetPrintFooter(false);
$pdf->AddPage();
// dejavusans входит в поставку TCPDF и содержит кириллицу
$pdf->SetFont('dejavusans', '', 12);
$pdf->Cell(0, 10, 'Счёт № 42 от 01.06.2024', 0, 1);
// вывод в файл: 'F' — сохранить, 'D' — отдать на скачивание
$pdf->Output('/tmp/invoice.pdf', 'F');Именно шрифт dejavusans из поставки TCPDF закрывает проблему кириллицы. Если подставите системный шрифт без нужных глиф — снова получите квадраты.
HTML в PDF: Dompdf и mPDF
Dompdf и mPDF берут HTML с CSS и превращают в PDF. Это соблазнительно: вёрстку счёта можно отдать вёрстальщику, а данные подставить через шаблонизатор. Но обе библиотеки поддерживают ограниченное подмножество CSS. Flexbox и grid не работают, сложные float ломаются, а на объёмных таблицах Dompdf заметно тормозит.
mPDF лучше держит кириллицу и печатные нюансы (колонтитулы, нумерация страниц), но тяжелее по памяти. Dompdf легче и проще ставится, но капризнее к вёрстке.
Правило, которое экономит часы: если документ — это счёт, договор или накладная со строгим макетом, верстайте таблицами и инлайновыми стилями. HTML-в-PDF — не браузер, современный CSS он не понимает.
Внешний движок: wkhtmltopdf и headless-браузер
Когда нужен настоящий рендеринг CSS, документ отдают внешнему движку. Раньше это был wkhtmltopdf, но проект официально в архиве и не развивается — использовать его в новых проектах рискованно. Актуальный путь — headless Chromium через сервис вроде gotenberg или библиотеку, которая дёргает браузер. Это даёт полноценный CSS, но добавляет внешнюю зависимость, которую нужно держать живой и мониторить.
Excel: XLSX это не «HTML с расширением .xls»
Первая ошибка — отдать HTML-таблицу с заголовком Content-Type: application/vnd.ms-excel. Excel такое откроет, но выдаст предупреждение о повреждённом файле, а формулы и типы данных сломаются. Настоящий XLSX — это ZIP-архив с XML внутри.
PhpSpreadsheet: удобно, но прожорливо
PhpSpreadsheet — стандарт де-факто. Умеет всё: стили, формулы, графики, объединённые ячейки, чтение и запись. Расплата — память. Каждая ячейка это объект, и на десятках тысяч строк потребление растёт линейно и быстро.
<?php
// PhpSpreadsheet 2.x, PHP 8.2
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Товар');
$sheet->setCellValue('B1', 'Цена');
$sheet->setCellValue('A2', 'Кофе');
$sheet->setCellValue('B2', 549);
$writer = new Xlsx($spreadsheet);
$writer->save('/tmp/report.xlsx');Частичное решение — включить сжатие ячеек в памяти. У PhpSpreadsheet есть механизм cell caching через настройку хранилища ячеек, но это лишь отодвигает потолок, не убирая линейный рост. Точный способ включения зависит от версии — сверьтесь с разделом Memory saving в документации.
Большие выгрузки: потоковая запись
Когда строк десятки и сотни тысяч, держать весь документ в памяти нельзя. Нужен writer, который пишет строки в файл потоком и не хранит их все сразу. Для этого есть отдельные библиотеки вроде box/spout (сейчас развивается как openspout/openspout). Они жертвуют богатыми стилями и формулами ради постоянного потребления памяти независимо от числа строк.
Совсем просто: CSV
Если получателю важны только данные, а не оформление, не мучайтесь с XLSX. CSV пишется штатной функцией fputcsv, потребляет константу памяти и открывается в Excel. Подводный камень один — кодировка: Excel в Windows ждёт либо BOM для UTF-8, либо windows-1251, иначе кириллица поедет.
Что выбирать под задачу
| Задача | Инструмент | Цена решения |
|---|---|---|
| Счёт, договор, строгий макет | TCPDF / mPDF | Вёрстка руками или ограниченный CSS |
| PDF со сложным CSS | Headless Chromium | Внешний сервис в инфраструктуре |
| Excel с формулами и стилями, до ~10к строк | PhpSpreadsheet | Высокое потребление памяти |
| Excel на десятки-сотни тысяч строк | openspout (потоковая запись) | Нет богатого форматирования |
| Только данные, оформление не нужно | CSV через fputcsv | Нет типов, стилей, листов |
Где всё ломается на проде
- Таймаут веб-запроса. Генерация тяжёлого документа в HTTP-обработчике блокирует воркер и упирается в
max_execution_time. Тяжёлые выгрузки выносите в очередь и отдавайте пользователю ссылку на готовый файл. - Память. PhpSpreadsheet на большом отчёте выбьет
memory_limit. Поднимать лимит до гигабайтов — не решение, а отложенная авария: несколько параллельных выгрузок положат сервер. - Кириллица. В PDF — из-за шрифта без глиф, в CSV — из-за кодировки без BOM. Обе беды всплывают только при открытии файла пользователем, не на этапе генерации.
- Диск /tmp. Промежуточные файлы копятся и забивают раздел. Чистите после отдачи или пишите во временный файл с автоудалением.
Что делать дальше
Определите порог: до нескольких тысяч строк или простого счёта хватит синхронной генерации в запросе. Выше — заводите фоновую задачу и потоковый writer. Замерьте пиковое потребление памяти на реальном объёме данных (memory_get_peak_usage(true)) до релиза, а не после жалоб. И проверяйте готовые файлы открытием в целевой программе — Excel на Windows и просмотрщик PDF ведут себя не так, как парсер в тестах.
Магистр Excel и Google Sheets: ВПР, СУММЕСЛИМН, макросы VBA и скрипты Apps Script.
Спросить за 20 ₽Источники: PhpSpreadsheet — официальная документация, TCPDF — официальный сайт
Частые вопросы
Кириллица в PDF выводится квадратами, что делать?
PhpSpreadsheet падает с Allowed memory size exhausted на большом отчёте
У меня другая версия PhpSpreadsheet, примеры сработают?
Можно ли просто отдать HTML-таблицу с расширением .xls?
wkhtmltopdf всё ещё можно использовать?
Генерация PDF в контроллере упирается в таймаут, как быть?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.