pdfapi

PHP

Именованные аргументы, понятные исключения, повторы при перегрузке. Без сторонних зависимостей — нужно только расширение curl.

ПакетВерсии PHPЗависимости
pdfapi/pdfapi8.2+ext-curl

Установка#

bash
composer require pdfapi/pdfapi

Ключ берётся из переменной окружения PDFAPI_KEY — выпустите его в кабинете:

bash
export PDFAPI_KEY=pdf_live_...

Первый PDF#

php
use PdfApi\Client;

$client = new Client();   // ключ из PDFAPI_KEY

$pdf = $client->html('<h1>Счёт №42</h1>', marginTop: '20mm');
$pdf->save('invoice.pdf');

echo $pdf->pages, ' стр., осталось ', $pdf->quota?->remaining;

Методы#

МетодЧто делает
html($html, ...параметры)HTML → PDF
url($url, ...параметры)публичная страница по адресу → PDF. Тариф Premium и выше; адрес — полный, с https://
markdown($text, ...параметры)Markdown → PDF. Тариф Premium и выше
office($path, $landscape = null)docx, xlsx, pptx с диска → PDF. Тариф Premium и выше
officeFromString($data, $filename, $landscape = null)то же из памяти
convert(array $body)POST /v1/convert с телом как есть
me()тариф, расход и остаток страниц — GET /v1/me

У каждого метода есть timeout: — секунд на этот вызов.

Параметры страницы#

Именованные аргументы, все необязательные. Что они значат — в разделе Параметры страницы.

ПараметрПримерПо умолчанию
landscapetruefalse
paperWidth, paperHeight'8.27in', '297mm'A4
marginTop, marginBottom, marginLeft, marginRight'20mm''0.39in'
waitDelay'2s', '500ms', до 10 секунд'0s'
php
$pdf = $client->url('https://example.com/report', landscape: true, waitDelay: '1s');

Размер — число и единица pt, px, in, mm, cm или pc без пробела. '20' и '20 mm' вызовут InvalidArgumentException сразу, без запроса к API.

Документы Office#

php
$pdf = $client->office('report.docx');                                    // с диска
$pdf = $client->officeFromString($data, 'table.xlsx', landscape: true);   // из памяти — имя с расширением

Файл — до 3,7 МБ: в base64 он вырастает на треть, а тело запроса ограничено 5 МБ. Библиотека проверяет, что файл — настоящий ZIP-архив, поэтому старый .doc или PDF, переименованный в .docx, отклоняется до отправки.

Результат#

Result:

  • content — байты PDF, строка;
  • pages — сколько страниц списано;
  • quota — Quota с полями limit, used и remaining после этой конвертации;
  • save($path) — записать в файл и вернуть путь.

save пишет атомарно: сначала во временный файл рядом, потом переименовывает поверх. Оборвётся запись или кончится место — останется прежний файл, а не обрезанный PDF.

Аккаунт и квота#

php
$me = $client->me();
echo "{$me->plan}: осталось {$me->pagesRemaining} из {$me->pagesLimit}, обнулится ",
    $me->periodEnd?->format(DATE_ATOM) ?? 'неизвестно когда', "\n";

Account: userId, plan, periodStart, periodEnd (DateTimeImmutable в UTC или null), pagesUsed, pagesLimit, pagesRemaining, requestsPerSecond, burst. Запрос страниц не тратит. Как встроить проверку в cron — в руководстве Контроль квоты.

Ошибки#

Все ошибки API — наследники PdfApi\Exception\PdfApiException с полями errorCode, status и сообщением по-русски.

php
use PdfApi\Exception\{QuotaExceededException, RateLimitException, RenderException, PdfApiException};

try {
    $pdf = $client->html($html);
} catch (QuotaExceededException $e) {
    // страницы кончились
} catch (RateLimitException $e) {
    sleep((int) ceil($e->retryAfter ?? 1));
} catch (RenderException $e) {          // и RenderTimeoutException тоже
    // разметка не отрендерилась
} catch (PdfApiException $e) {
    error_log("{$e->errorCode} {$e->status}: {$e->getMessage()}");
}
ИсключениеКогда
AuthenticationException401: ключа нет, он неверный или отозван
PermissionDeniedException403: почта не подтверждена или формат недоступен на тарифе
QuotaExceededException402: страницы периода кончились или документ больше остатка
InvalidRequestException400: неверный параметр или адрес во внутреннюю сеть (errorCode === 'url_not_allowed')
PayloadTooLargeException413: тело больше 5 МБ; наследник InvalidRequestException
RateLimitException429: слишком часто; retryAfter — через сколько секунд можно снова
RenderException502: документ не отрендерился
RenderTimeoutException504: рендер дольше 30 секунд; наследник RenderException
UnavailableException503: рендер временно недоступен
ApiConnectionExceptionнет связи с API
ApiTimeoutExceptionAPI не ответил за таймаут; наследник ApiConnectionException

Неверные аргументы — пустой HTML, адрес без https://, файл .pdf вместо .docx — \InvalidArgumentException до запроса; лимит частоты на них не тратится. Все коды API — в разделе Ошибки и повторы.

Повторы#

Библиотека сама повторяет запрос, когда это безопасно, — по умолчанию до двух раз (maxRetries):

  • 429 — через Retry-After, но не дольше минуты;
  • 503 или нет соединения — первый повтор примерно через 0,5 секунды, второй — примерно через 1 секунду.
Таймауты не повторяются

После таймаута результат может быть уже сохранён. Повтор с тем же Idempotency-Key и телом запроса защищает от повторного списания на обновлённом сервере. Без этого ключа повтор считается новой конвертацией. Подробнее.

Настройки клиента#

php
$client = new Client(
    apiKey: 'pdf_live_...',               // иначе PDFAPI_KEY
    baseUrl: 'https://pdfapi.ru/api',     // иначе PDFAPI_BASE_URL
    timeout: 40.0,                        // секунд на запрос
    maxRetries: 2,
    transport: $myTransport,              // свой PdfApi\Http\Transport: прокси, PSR-18
);
  • Неверные настройки — таймаут не больше нуля или отрицательное число повторов — \InvalidArgumentException сразу.
  • Ключ не утекает — его нет в var_dump и print_r клиента, а в трассировках исключений он помечен #[\SensitiveParameter].

PDF в веб-приложении#

PDF уже в памяти — отдавайте байты, на диск сохранять незачем:

php
return response($pdf->content, 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'inline; filename="invoice.pdf"',
]);

Данные пользователя в HTML — только через htmlspecialchars или шаблоны с автоэкранированием (Blade, Twig), иначе это HTML-инъекция в документ — см. Безопасность и данные.

Примеры#

В папке examples/ библиотеки; запуск — composer install && PDFAPI_KEY=pdf_live_... php examples/<файл>.

ФайлЧто показывает
invoice.phpсчёт из HTML-шаблона с экранированием данных
from-url.phpстраница по адресу, альбомная, с ожиданием скриптов
office.phpdocx, xlsx или pptx в PDF рядом с исходным файлом
batch.phpпачка документов по очереди — при 429 библиотека подождёт сама
quota.phpостаток страниц для cron: код выхода 2 при нехватке
web.phpотдача PDF без фреймворка: php -S 127.0.0.1:8000 examples/web.php