Przejdź do głównej zawartości

Zwracanie wygenerowanego pliku PDF z kontrolera

Wygeneruj plik Portable Document Format (PDF) w akcji kontrolera i zwróć go jako odpowiedź Hypertext Transfer Protocol (HTTP). Każda integracja z frameworkiem udostępnia pomocnika PdfResponse, który buduje obiekt odpowiedzi, ustawia Content-Type: application/pdf, dołącza nagłówki bezpieczeństwa i sanityzuje nazwę pliku. Ten przewodnik omawia trzy tryby dostarczania we frameworkach Laravel, Symfony i CodeIgniter 4: podgląd inline, pobranie pliku oraz dostarczanie strumieniowe.

Najpierw sprawdź poniższe wymagania wstępne, aby ścieżka kontrolera była gotowa, zanim zaczniesz:

  • Rdzeń NextPDF jest zainstalowany.
  • Zainstalowana jest jedna integracja z frameworkiem, a jej dostawca usług, pakiet (bundle) lub usługa zostały wykryte. Zweryfikuj wykrycie na stronie instalacji swojego frameworka, zanim zaczniesz.
  • Tryb strumieniowy nie wymaga dodatkowych pakietów. Każda integracja zawiera wariant strumieniowy obok buforowanego.

To poradnik how-to. Zakłada, że potrafisz już skierować żądanie do kontrolera w swoim frameworku. Pierwszy uruchamialny przykład dla każdego frameworka znajdziesz w przewodniku szybkiego startu, do którego odsyła sekcja Zobacz też.

Zainstaluj integrację dla swojego frameworka. Uruchom jedno z poniższych poleceń.

Okno terminala
composer require nextpdf/laravel
Okno terminala
composer require nextpdf/symfony
Okno terminala
composer require nextpdf/codeigniter

W przypadku Laravela opublikuj konfigurację po instalacji.

Okno terminala
php artisan vendor:publish --tag=nextpdf-config

Symfony rejestruje pakiet (bundle) za pomocą Flex, a CodeIgniter wykrywa usługę automatycznie. Potwierdź wykrycie na stronie instalacji swojego frameworka, zanim przejdziesz dalej.

Każda integracja z frameworkiem ma ten sam trzyetapowy schemat: pobierasz świeży dokument, zapisujesz do niego treść i przekazujesz go do fabryki PdfResponse, która zwraca odpowiedź HTTP. API dokumentu (addPage(), cell(), setFont()) należy do publicznej powierzchni rdzenia silnika i jest identyczne we wszystkich frameworkach. Fabryka odpowiedzi różni się tylko zwracaną klasą odpowiedzi, ponieważ każdy framework ma własny typ odpowiedzi HTTP.

PdfResponse oferuje trzy tryby dostarczania. Tryb inline ustawia nagłówek Content-Disposition: inline, więc przeglądarka renderuje plik PDF w karcie podglądu. Tryb pobierania ustawia Content-Disposition: attachment, więc przeglądarka zapisuje plik. Tryb strumieniowy emituje treść pliku PDF w stałych fragmentach, zamiast buforować cały dokument w pamięci. Wybierz go dla dużych dokumentów, gdy szczytowe zużycie pamięci ma większe znaczenie niż znana wartość Content-Length.

Uzyskaj dokument standardową ścieżką rozwiązywania zależności w swoim frameworku:

  • Laravel — rozwiąż NextPDF\Contracts\DocumentFactoryInterface z kontenera za pomocą app(...) i wywołaj create(); zwróci to świeży NextPDF\Core\Document — konkretny typ, który akceptują fabryki PdfResponse.
  • Symfony — wstrzyknij NextPDF\Symfony\Service\PdfFactory i wywołaj create(); zwróci to świeży NextPDF\Core\Document z już zastosowaną konfiguracją wartości domyślnych dokumentu.
  • CodeIgniter 4 — rozwiąż bibliotekę Pdf przez Services::pdf() (lub pomocnika pdf()) albo uzyskaj sam dokument przez pdf_document().
ZagadnienieLaravelSymfonyCodeIgniter 4
Świeży dokumentapp(DocumentFactoryInterface::class)->create()PdfFactory::create()pdf_document() / Services::pdf()->document()
Odpowiedź inlinePdfResponse::inline($doc, $name)PdfResponse::inline($doc, $name)$pdf->inline($name) / PdfResponse::inline($doc, $name)
Odpowiedź pobieraniaPdfResponse::download($doc, $name)PdfResponse::download($doc, $name)$pdf->download($name) / PdfResponse::download($doc, $name)
Strumieniowy inlinePdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)
Strumieniowe pobieraniePdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)
Zwracany typIlluminate\Http\Response (strumieniowo: StreamedResponse)Symfony\Component\HttpFoundation\Response (strumieniowo: StreamedResponse)CodeIgniter\HTTP\DownloadResponse

PdfResponse dla Laravel znajduje się w NextPDF\Laravel\Http\PdfResponse, dla Symfony w NextPDF\Symfony\Http\PdfResponse, a dla CodeIgniter w NextPDF\CodeIgniter\Http\PdfResponse. Strona security-and-operations każdej integracji dokumentuje pełne zachowanie odpowiedzi w danym pakiecie: zestaw nagłówków, reguły dyspozycji oraz sanityzację nazwy pliku. Linki do tych stron znajdziesz w sekcji Zobacz też.

Poniżej znajduje się minimalna akcja pobierania dla każdego frameworka. Wywołania na dokumencie korzystają z tej samej powierzchni rdzenia. Zmienia się tylko szkielet kontrolera.

Laravel: app/Http/Controllers/ReportController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller
{
public function download(): Response
{
$document = app(DocumentFactoryInterface::class)->create();
$document->addPage();
$document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf');
}
}
Symfony: src/Controller/ReportController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;
use NextPDF\Symfony\Service\PdfFactory;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ReportController
{
#[Route('/report', name: 'report_pdf')]
public function download(PdfFactory $pdf): Response
{
$document = $pdf->create();
$document->addPage();
$document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf');
}
}
CodeIgniter 4: app/Controllers/ReportController.php
<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController
{
public function download(): DownloadResponse
{
$pdf = Services::pdf();
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf');
}
}

Aby wyświetlić podgląd w przeglądarce zamiast pobierać plik, zamień wywołanie download(...) na inline(...) w Laravelu i Symfony albo na $pdf->inline('report.pdf') w CodeIgniterze. Dyspozycja zmienia się na inline, a wszystkie pozostałe nagłówki pozostają takie same.

W kodzie produkcyjnym akcja wstrzykuje zależności, przechwytuje najbardziej szczegółowy wyjątek udokumentowany przez integrację, loguje klasę awarii bez ujawniania śladu stosu i zwraca zdefiniowany błąd HTTP. Poniższy przykład korzysta ze wstrzykiwania przez konstruktor w Laravelu. Odpowiedniki dla Symfony i CodeIgnitera mają ten sam schemat i znajdują się na stronie production-usage każdej integracji.

Laravel: app/Http/Controllers/InvoiceController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly DocumentFactoryInterface $documents,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$document = $this->documents->create();
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download(
$document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Log the exception class, never the message or a stack trace,
// so internal detail does not leak into the log sink.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

Wstrzyknij DocumentFactoryInterface i wywołaj create() w każdej akcji. Zwraca to świeży NextPDF\Core\Document — konkretny typ, który akceptują fabryki PdfResponse dla Laravel. Rozwiązywanie świeżego dokumentu przy każdym żądaniu sprawia, że fabryka pozostaje wymienialna w testach. Nie używaj ponownie jednej instancji kontrolera dla dwóch niepowiązanych dokumentów w obrębie jednego długo działającego procesu roboczego.

W przypadku bardzo dużych dokumentów zamiast buforowanej fabryki użyj wariantu strumieniowego, aby ograniczyć szczytowe zużycie pamięci. Wariant strumieniowy zwraca StreamedResponse (Laravel i Symfony) i emituje treść w stałych fragmentach. Celowo pomija Content-Length, więc paski postępu pobierania i serwery proxy wrażliwe na długość nie otrzymują znanego rozmiaru. W przypadku małych odpowiedzi wrażliwych na opóźnienie preferuj buforowane download() / inline().

Laravel: streamed download for a large report
$document = $this->documents->create();
// ... emit content onto $document ...
return PdfResponse::streamDownload($document, 'annual-report.pdf');
  • Świeży dokument na każde wywołanie. We wszystkich trzech integracjach dokument jest produktem fabryki, tworzonym od nowa przy każdym rozwiązaniu. Nie buforuj rozwiązanego dokumentu między logicznymi dokumentami ani między żądaniami w długo działającym procesie roboczym. W przeciwnym razie dalej przejdzie nieaktualny stan treści.
  • Pusta nazwa pliku. Gdy do fabryki PdfResponse przekażesz pustą nazwę pliku, zostanie użyta wartość domyślna (document.pdf) zamiast pustej dyspozycji. Przekaż jawną, znaczącą nazwę pliku.
  • Nazwy plików spoza ASCII. Odpowiedź w Laravelu automatycznie dodaje parametr RFC 5987 filename*= dla nazw spoza ASCII, a dla nazw ASCII używa zwykłego parametru. Nie koduj nazwy pliku samodzielnie.
  • Odpowiedzi strumieniowe za buforującym serwerem proxy. Serwer proxy buforujący całą treść niweczy korzyść pamięciową ze strumieniowania. Skonfiguruj serwer proxy tak, aby strumieniował odpowiedzi PDF, albo użyj odpowiedzi buforowanej dla tej ścieżki.
  • Strumieniowe wywołanie zwrotne w Symfony. Strumieniowy wariant Symfony zwraca StreamedResponse, którego wywołanie zwrotne opróżnia wyjście. Nie zapisuj samodzielnie do treści odpowiedzi po jej zwróceniu.

Synchroniczne generowanie wewnątrz kontrolera blokuje żądanie na cały czas budowania pliku PDF. Dokument jednostronicowy zwykle dobrze mieści się w typowym budżecie żądania. W przypadku wyjścia wielostronicowego lub wsadowego przenieś generowanie poza wątek żądania, korzystając z zadania w kolejce — zobacz Generowanie pliku PDF w zadaniu w kolejce. Warianty strumieniowe zmniejszają szczytowe zużycie pamięci dla dużych dokumentów kosztem nieznanej wartości Content-Length. Wybierz je, gdy ograniczeniem jest pamięć, a pasek postępu nie jest wymagany.

  • Fabryki PdfResponse stosują stały zestaw nagłówków wzmacniających bezpieczeństwo odpowiedzi i sanityzują nazwę pobieranego pliku w każdej integracji. Nie dodawaj tych nagłówków samodzielnie.
  • Nigdy nie wstawiaj niezwalidowanych danych wejściowych użytkownika bezpośrednio do nazwy pliku przekazywanej do fabryki. Przekaż wartość, którą kontrolujesz, i pozwól fabryce zsanityzować ją w drugiej warstwie.
  • W bloku catch loguj klasę wyjątku i identyfikator korelacji, a nie komunikat wyjątku ani ślad stosu. Surowy ślad stosu trafiający do logów to wyciek informacji.
  • Nigdy nie pisz pustego bloku catch. Każdy przykład tutaj loguje i zwraca zdefiniowaną odpowiedź błędu.

Strona security-and-operations każdej integracji dokumentuje model zagrożeń tej integracji: zestaw nagłówków, reguły sanityzacji nazwy pliku oraz moment wiązania dokumentu.

Ten przewodnik nie formułuje żadnego normatywnego stwierdzenia dotyczącego standardów. Każde pokazane wywołanie API to zweryfikowana publiczna powierzchnia wskazanej integracji, sprawdzona względem stron quickstart i production-usage każdego pakietu. Nadrzędne strony production-usage, podlinkowane w sekcji Zobacz też, dokumentują semantykę nagłówków i zachowanie wiązania w kontenerze, na których opierają się integracje, wraz z cytowaniami PSR. Ta strona poradnika ponownie pokazuje sposób użycia i odsyła po normatywne cytowania do tamtych stron.