Przejdź do głównej zawartości

NextPDF Symfony w produkcji

Używaj pakietu w długo działających środowiskach uruchomieniowych PHP. Pakiet tworzy niewspółdzielone dokumenty, blokuje rejestr czcionek po rozgrzaniu i resetuje pamięć podręczną obrazów między żądaniami. Duże pliki w formacie PDF (Portable Document Format) strumieniuj, a ciężkie zadania przekazuj do procesów roboczych Messenger.

Długo działające środowiska uruchomieniowe utrzymują kontener przy życiu między żądaniami, dlatego stan związany z konkretnym żądaniem musi pozostać odizolowany. Procesy robocze FrankenPHP, RoadRunner i Messenger działają zgodnie z tym modelem. Plik services.php pakietu definiuje poniższy cykl życia, zweryfikowany względem definicji usług:

  • Document — niewspółdzielony. nextpdf.document (oraz aliasy PdfDocumentInterface / Document) zwraca nową instancję przy każdym rozwiązaniu. Zgodnie z PSR-11 (PHP Standard Recommendation 11) kontener może prawidłowo zwracać inną wartość przy każdym wywołaniu get() dla tego samego identyfikatora (PSR-11 §1.1.2). Rozwiązuj dokument osobno dla każdego żądania. Nigdy nie przechowuj go między żądaniami.
  • FontRegistry — współdzielony i zablokowany. Rejestr jest singletonem przez cały czas życia procesu. Po warmup() (gdy preload_fonts nie jest puste) etap kompilatora wywołuje lock(). Blokada zapobiega modyfikacjom w czasie działania oraz zanieczyszczeniu stanu czcionek między żądaniami.
  • ImageRegistry — współdzielony, resetowany przy każdym żądaniu. Ograniczona pamięć podręczna obrazów według algorytmu najdawniej używanych (LRU) jest współdzielona, ale ma tag kernel.reset z metodą reset, dzięki czemu Symfony czyści ją między żądaniami w środowiskach uruchomieniowych respektujących kernel.reset.
  • Kontrakty EInvoice — niewspółdzielone. Gdy dostępne są implementacje Premium, usługi osadzania, walidatora, profilu i schematronu są rejestrowane jako niewspółdzielone. Kontekst parsera pozostaje ograniczony do pojedynczego wywołania i nigdy nie wycieka między żądaniami.

Wstrzyknij PdfFactory, czyli współdzielony, bezstanowy obiekt przechowujący konfigurację, i wywołuj create() osobno dla każdego żądania:

public function __construct(private readonly PdfFactory $pdf) {}
public function action(): Response
{
$doc = $this->pdf->create(); // fresh, disposable
// ... build ...
return PdfResponse::inline($doc, 'document.pdf');
}

Nie wstrzykuj Document ani nextpdf.document do współdzielonej usługi przechowywanej między żądaniami. Zamiast tego rozwiązuj je wewnątrz metody o zasięgu żądania.

PdfResponse::streamDownload() i streamInline() zwracają obiekt StreamedResponse. Wywołanie zwrotne emituje treść PDF w porcjach po 64 KB i opróżnia bufor po każdej z nich, co ogranicza rozmiar bufora odpowiedzi dla dużych dokumentów. Poniższe zachowania zostały zweryfikowane względem PdfResponse:

  • Warianty strumieniowe celowo pomijają nagłówek Content-Length, ponieważ obiekt odpowiedzi nie zna z góry rozmiaru treści. Paski postępu pobierania i niektóre serwery proxy działają lepiej przy znanej długości. Użyj niestrumieniowych download() lub inline(), gdy dokument jest na tyle mały, że mieści się w pamięci, a znana długość treści jest pożądana.
  • Warianty strumieniowe emitują te same nagłówki bezpieczeństwa oraz ten sam Cache-Control: private, max-age=0, must-revalidate co warianty buforowane.

Wybieraj strumieniowanie dla wielomegabajtowych raportów i eksportów wsadowych. Warianty buforowane wybieraj dla małych odpowiedzi wrażliwych na opóźnienia.

Przekazuj generowanie do Messenger, gdy żądania muszą szybko zwracać odpowiedź albo renderowanie obciąża procesor.

  1. Zaimplementuj PdfBuilderInterface dla każdego typu dokumentu.
  2. Zarejestruj buildery w container.service_locator i podłącz go jako $builderLocator obiektu GeneratePdfHandler.
  3. Skieruj GeneratePdfMessage do trwałego transportu.
  4. Uruchamiaj procesy robocze z ograniczonym czasem życia.

Odświeżaj procesy robocze, aby wyciek pamięci w zależności zewnętrznej nie mógł rosnąć bez ograniczeń:

Okno terminala
php bin/console messenger:consume async \
--limit=200 \
--memory-limit=256M \
--time-limit=3600

Klucze konfiguracyjne pakietu messenger.timeout i messenger.retries zapisują zamierzony limit czasu na wiadomość oraz budżet ponownych prób. Wymuś takie samo zachowanie za pomocą strategii ponawiania Symfony oraz flag procesu roboczego.

Bezpieczeństwo ścieżki wyjściowej w procesach roboczych

Dział zatytułowany „Bezpieczeństwo ścieżki wyjściowej w procesach roboczych”

GeneratePdfMessage weryfikuje ścieżkę wyjściową podczas konstruowania. GeneratePdfHandler weryfikuje ją ponownie w czasie wykonania, przed zapisem na dysk. Ta dwuetapowa kontrola ma znaczenie w pracy asynchronicznej. Wiadomość może pozostawać w kolejce między wysłaniem a odebraniem, dlatego handler nie ufa ślepo ścieżce z kolejki. Ogranicz uprawnienia systemu plików procesu roboczego do zamierzonego katalogu wyjściowego jako element obrony w głąb.

Usługi FontRegistry i ImageRegistry przyjmują opcjonalny Psr\Log\LoggerInterface (powiązany przez nullOnInvalid()). Gdy aplikacja dostarcza logger, rejestry mogą emitować za jego pośrednictwem dane diagnostyczne. Logger jest opcjonalnym, wymiennym współpracownikiem zgodnym z kontraktem loggera PSR-3 (PSR-3). Aby uzyskać widoczność na poziomie żądania, dodaj logowanie wokół PdfFactory::create() oraz handlera Messenger w kodzie aplikacji. Podczas analizy incydentów używaj messenger:consume -vv.

  • Przypnij jedną wersję główną nextpdf/core w pliku composer.json aplikacji (pakiet akceptuje ^3.0 || ^5.2).
  • Upewnij się, że ext-mbstring i ext-zlib są włączone we wdrożonym obrazie PHP (w przeciwnym razie pakiet zawodzi natychmiast przy starcie).
  • Wstępnie wypełnij preload_fonts czcionkami, których używają dokumenty, aby rejestr rozgrzewał się i blokował przy starcie, a nie przy pierwszym żądaniu.
  • Ustaw cache_path na zapisywalną, trwałą lokalizację, jeśli polegasz na artefaktach z pamięci podręcznej między wdrożeniami. W przeciwnym razie domyślna wartość %kernel.cache_dir% jest wystarczająca.
  • Uruchom php bin/console cache:warmup podczas wdrażania, aby skompilowany kontener (w tym sondy opcjonalnych rozszerzeń) został zbudowany przed napływem ruchu.
  • Używaj trwałego transportu Messenger (nie sync) do produkcyjnej pracy asynchronicznej i odświeżaj procesy robocze za pomocą --limit / --memory-limit / --time-limit.
  • Odpowiedzi strumieniowe za buforującym serwerem proxy — serwer proxy buforujący całą treść niweczy korzyść pamięciową. Skonfiguruj serwer proxy tak, aby strumieniował odpowiedzi PDF, albo użyj tam odpowiedzi buforowanych.
  • kernel.reset nieobsługiwany — w środowisku uruchomieniowym, które nie wywołuje kernel.reset, pamięć podręczna obrazów jest ograniczona przez image_cache_mb, ale nie jest czyszczona między żądaniami; odpowiednio dobierz limit.
  • Przechowywanie dokumentu między żądaniami — przechwycony Document z wcześniejszego żądania będzie zawierał nieaktualny stan. Zawsze rozwiązuj go osobno dla każdego żądania za pomocą PdfFactory.

Każdy wiersz jest normatywnym stwierdzeniem na tej stronie, przypiętym do pełnego 64-znakowego szesnastkowego reference_id z bramkowanego korpusu organizacji opracowującej standardy (SDO). Pochodzenie manifestu korpusu i transportu pobierania znajduje się w _sidecars/rag-citations.yaml.

SpecyfikacjaKlauzulareference_idStwierdzenie
PSR-11psr_11_container#1.1.2.p3.bUsługa niewspółdzielona: odrębna wartość przy każdym rozwiązaniu
PSR-3psr_3_logger#x3.p17Opcjonalny współpracownik typu logger
  • /integrations/symfony/configuration/ — cykl życia usług i parametry.
  • /integrations/symfony/security-and-operations/ — nagłówki odpowiedzi, walidacja ścieżek, obsługa kluczy.
  • /integrations/symfony/troubleshooting/ — diagnostyka startu i czasu działania.
  • /integrations/symfony/quickstart/ — minimalna konfiguracja asynchroniczna.