Перейти к содержимому

Artisan в продакшене

В продакшене внедряйте настроенный модуль отрисовки и логгер PHP Standards Recommendation 3 (PSR-3), повторно используйте запущенный процесс Chrome между отрисовками, задавайте явную высоту для многоэлементных документов и ограничивайте путь отрисовки внешним тайм-аутом выше по цепочке.

BrowserPool поддерживает один запущенный процесс Chrome (keepAlive: true) и перезапускает его каждые 100 отрисовок, чтобы ограничить рост потребления памяти — известный сценарий накопления у долгоживущих клиентов Chrome DevTools Protocol (CDP). Для обработчика, который отрисовывает много документов, используйте один долгоживущий модуль отрисовки вместо отдельного модуля на каждый запрос — так затраты на запуск Chrome будут возникать редко.

render-service.php
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
use NextPDF\Artisan\Exception\ChromeNotAvailableException;
use NextPDF\Artisan\Exception\ChromeRenderException;
use Psr\Log\LoggerInterface;
final class ReportRenderer
{
private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger)
{
$config = ChromeRendererConfig::fromArray([
'chrome_binary' => getenv('CHROME_BINARY') ?: null,
'render_timeout' => 45,
'max_html_size' => 2_000_000,
'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'),
]);
$this->renderer = new ChromeHtmlRenderer($config, $logger);
}
public function render(string $html, float $widthPt, float $heightPt = 0.0): string
{
try {
return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData();
} catch (ChromeNotAvailableException $e) {
// Deployment fault: Chrome runtime missing. Page the on-call owner.
throw $e;
} catch (ChromeRenderException $e) {
// Render-time fault: timeout, crash, empty output. Retryable once.
throw $e;
}
}
public function shutdown(): void
{
$this->renderer->close();
}
}

Создайте модуль отрисовки один раз, затем используйте его повторно. Вызывайте close() при завершении работы обработчика, чтобы детерминированно освободить процесс Chrome, а не ждать деструктора. Две ветви catch разделяют сбой развёртывания (отсутствует среда выполнения) и сбой во время отрисовки (при котором возможен повтор). Не используйте пустые блоки catch.

Подключите его в контейнер как синглтон:

$container->singleton(ReportRenderer::class, fn ($c) =>
new ReportRenderer($c->get(Psr\Log\LoggerInterface::class)));

Если вы не задаёте высоту, мост измеряет высоту содержимого в Chrome (max из высот прокрутки и смещения body/document), преобразует её в пункты и добавляет страховочный запас ~0.2 дюйма (~14.4 pt). Этот запас покрывает расхождение между макетом области просмотра Chrome и перекомпоновкой под печатный макет. Без него printToPDF может перенести содержимое на вторую страницу, которую PageImporter (только страница 0) обрежет. Мост задаёт минимальную высоту листа в 0.1 дюйма. Это поведение проверяют тесты ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted и ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight.

Для документов с фиксированным макетом (счета, сертификаты) передавайте явную высоту в пунктах. Когда высота задана явно, запас не добавляется, а результат точно соответствует запрошенному размеру листа (проверяется тестом ::renderHonorsExplicitHeightWithoutAutoBuffer).

  • Создавайте один модуль отрисовки на обработчик и используйте его повторно. BrowserPool повторно использует запущенный браузер и автоматически перезапускает его на границе 100 отрисовок.
  • Вызывайте close() при завершении работы обработчика и между крупными пакетами, когда вам нужен свежий процесс Chrome раньше, чем наступит граница 100 отрисовок.
  • Деструктор вызывает close(), но явный вызов close() детерминирован и предпочтителен в длительно работающих процессах.
  • Уведомления о перезапуске записываются на уровне notice вместе со счётчиком отрисовок; настройте оповещение при повышенной частоте перезапусков, потому что это указывает на документы тяжелее ожидаемого.

Внедрите логгер PSR-3. Модуль отрисовки генерирует следующие события и уровни:

СобытиеУровеньКонтекст
Начало отрисовкиdebugsize, width, height
Отрисовка завершенаdebugpdfSize, contentHeight
Запуск браузераinfobinary
Перезапуск браузераnoticecount
Закрытие браузераdebugrenderCount

Ни HTML, ни байты PDF, ни извлечённый текст в журнал не записываются. Так полезная нагрузка не попадает в операционные журналы, что соответствует рекомендациям National Institute of Standards and Technology Special Publication (NIST SP) 800-92 по содержимому журналов. Стройте цели уровня обслуживания (SLO) по задержке на основе пары start/complete, а оповещение о частоте перезапусков — на основе событий notice.

  • Sidecar-контейнер с Chrome: запускайте Chrome в том же контейнере, что и обработчик PHP; зафиксируйте chrome_binary. Подготовьте контейнер с поддержкой песочницы; см. /integrations/artisan/chrome-renderer-setup/.
  • Без контейнера / CLI: в Artisan нет контейнера внедрения зависимостей. Используйте EInvoiceServiceFactory для контрактов электронных счетов Premium в исполнителях командной строки (CLI); см. /integrations/artisan/boot-and-discovery/.
  • Ограничение ресурсов: сочетайте render_timeout с бюджетом запроса выше по цепочке и хостовыми cgroup/ulimit. См. модель угроз на /integrations/artisan/security-and-operations/.
  • Модуль отрисовки, прерванный в середине отрисовки, всё равно закрывает страницу Chrome (finally), поэтому пул остаётся пригодным к использованию.
  • Повторное использование одного модуля отрисовки между threads/processes не поддерживается; один модуль отрисовки владеет одним процессом Chrome.
  • Перезапуск через каждые 100 отрисовок фиксирован; рассчитывайте размер пакетов с учётом этого, чтобы всплески задержки оставались предсказуемыми.

Затраты в установившемся режиме — это компоновка входных данных в Chrome плюс printToPDF, а не накладные расходы моста. keepAlive распределяет затраты на запуск по нескольким отрисовкам. Ожидайте всплеск задержки на каждой 100-й отрисовке (перезапуск процесса); отражайте его в SLO, а не рассматривайте как инцидент.

В продакшене на вход поступает недоверенный HTML. Перечитайте /integrations/artisan/security-and-operations/. Сетевые барьеры действуют независимо от конфигурации, но no_sandbox: true отключает изоляцию процесса Chrome и повышает требования к доверию к входным данным.

В обработчиках без контейнера EInvoiceServiceFactory возвращает null, когда Premium не установлен, поэтому путь отрисовки с открытым исходным кодом продолжает работать без изменений. Установите Pro/Enterprise, чтобы включить встраивание и проверку электронных счетов в отрисованном документе.

  • Быстрый старт — /integrations/artisan/quickstart/
  • Настройка моста Chrome — /integrations/artisan/configuration/
  • Безопасность и эксплуатация — /integrations/artisan/security-and-operations/
  • Настройка средства отрисовки Chrome — /integrations/artisan/chrome-renderer-setup/
  • Устранение неполадок — /integrations/artisan/troubleshooting/