Lewati ke konten

Mengembalikan PDF yang dihasilkan dari controller

Hasilkan berkas Portable Document Format (PDF) dalam aksi controller dan kembalikan sebagai respons Hypertext Transfer Protocol (HTTP). Setiap integrasi framework menyertakan helper PdfResponse yang membuat objek respons, menetapkan Content-Type: application/pdf, melampirkan header keamanan, dan membersihkan nama berkas. Panduan ini mencakup tiga mode pengiriman: pratinjau inline, unduhan berkas, dan pengiriman streaming untuk Laravel, Symfony, dan CodeIgniter 4.

Periksa prasyarat berikut lebih dulu agar jalur controller siap sebelum Anda mulai:

  • Core NextPDF sudah terpasang.
  • Satu integrasi framework sudah terpasang, dan service provider, bundle, atau service-nya telah terdeteksi. Verifikasi deteksinya di halaman instalasi framework Anda sebelum memulai.
  • Mode streaming tidak memerlukan paket tambahan. Setiap integrasi menyertakan varian streaming bersama varian buffered.

Ini panduan praktis. Panduan ini mengasumsikan Anda sudah tahu cara merutekan request ke controller di framework Anda. Untuk contoh awal yang dapat dijalankan di tiap framework, baca quickstart framework yang ditautkan di bagian Lihat juga.

Pasang integrasi untuk framework Anda. Jalankan salah satu perintah berikut.

Terminal window
composer require nextpdf/laravel
Terminal window
composer require nextpdf/symfony
Terminal window
composer require nextpdf/codeigniter

Untuk Laravel, publikasikan konfigurasi setelah instalasi.

Terminal window
php artisan vendor:publish --tag=nextpdf-config

Symfony mendaftarkan bundle melalui Flex, dan CodeIgniter mendeteksi service secara otomatis. Konfirmasikan deteksinya di halaman instalasi framework Anda sebelum melanjutkan.

Setiap integrasi framework mengikuti pola tiga bagian yang sama: Anda mendapatkan dokumen baru, menulis konten ke dokumen itu, lalu meneruskannya ke factory PdfResponse yang mengembalikan respons HTTP. API dokumen (addPage(), cell(), setFont()) merupakan bagian dari permukaan engine inti, dan sama persis di semua framework. Perbedaan factory respons hanya terletak pada kelas respons yang dikembalikan, karena setiap framework memiliki tipe respons HTTP sendiri.

PdfResponse menyediakan tiga mode pengiriman. Inline menetapkan header Content-Disposition: inline, sehingga browser merender PDF di tab penampil. Download menetapkan Content-Disposition: attachment, sehingga browser menyimpan berkas. Streamed mengeluarkan body PDF dalam potongan berukuran tetap alih-alih menyangga seluruh dokumen di memori. Pilih mode ini untuk dokumen besar ketika memori puncak lebih penting daripada Content-Length yang diketahui.

Dapatkan dokumen melalui mekanisme resolusi biasa di framework Anda:

  • Laravel — resolve NextPDF\Contracts\DocumentFactoryInterface dari container dengan app(...) dan panggil create(), yang mengembalikan NextPDF\Core\Document baru — tipe konkret yang diterima factory PdfResponse.
  • Symfony — injeksikan NextPDF\Symfony\Service\PdfFactory dan panggil create(), yang mengembalikan NextPDF\Core\Document baru dengan default dokumen yang sudah dikonfigurasi diterapkan.
  • CodeIgniter 4 — resolve library Pdf melalui Services::pdf() (atau helper pdf()), atau peroleh dokumen polos melalui pdf_document().
KebutuhanLaravelSymfonyCodeIgniter 4
Dokumen baruapp(DocumentFactoryInterface::class)->create()PdfFactory::create()pdf_document() / Services::pdf()->document()
Respons inlinePdfResponse::inline($doc, $name)PdfResponse::inline($doc, $name)$pdf->inline($name) / PdfResponse::inline($doc, $name)
Respons downloadPdfResponse::download($doc, $name)PdfResponse::download($doc, $name)$pdf->download($name) / PdfResponse::download($doc, $name)
Inline streamingPdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)
Download streamingPdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)
Tipe yang dikembalikanIlluminate\Http\Response (streaming: StreamedResponse)Symfony\Component\HttpFoundation\Response (streaming: StreamedResponse)CodeIgniter\HTTP\DownloadResponse

PdfResponse Laravel berada di NextPDF\Laravel\Http\PdfResponse, milik Symfony di NextPDF\Symfony\Http\PdfResponse, dan milik CodeIgniter di NextPDF\CodeIgniter\Http\PdfResponse. Halaman keamanan dan operasi setiap integrasi mendokumentasikan perilaku respons lengkap untuk paket tersebut: kumpulan header, aturan disposisi, dan pembersihan nama berkas. Halaman tersebut ditautkan di bagian Lihat juga.

Berikut aksi download minimal di tiap framework. Pemanggilan dokumen menggunakan permukaan inti yang sama. Hanya kerangka controller-nya yang berubah.

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');
}
}

Untuk menampilkan pratinjau di browser alih-alih mengunduhnya, ganti pemanggilan download(...) dengan inline(...) di Laravel dan Symfony, atau $pdf->inline('report.pdf') di CodeIgniter. Disposisi berubah menjadi inline, dan semua header lainnya tetap sama.

Aksi produksi menginjeksikan dependensinya, menangkap exception paling spesifik yang didokumentasikan integrasi, mencatat kelas kegagalan tanpa membocorkan trace, dan mengembalikan error HTTP yang terdefinisi. Contoh di bawah ini menggunakan injeksi melalui constructor Laravel. Versi setara untuk Symfony dan CodeIgniter mengikuti bentuk yang sama dan tersedia di halaman penggunaan produksi tiap integrasi.

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);
}
}
}

Injeksikan DocumentFactoryInterface dan panggil create() di setiap aksi. Ini mengembalikan NextPDF\Core\Document baru — tipe konkret yang diterima factory PdfResponse Laravel. Dengan me-resolve dokumen baru per request, factory tetap dapat ditukar saat pengujian. Jangan menggunakan ulang satu instance controller untuk dua dokumen yang tidak terkait dalam satu proses worker yang berjalan lama.

Untuk dokumen yang sangat besar, ganti factory buffered dengan yang streaming untuk membatasi memori puncak. Varian streaming mengembalikan StreamedResponse (Laravel dan Symfony) dan mengeluarkan body dalam potongan berukuran tetap. Varian ini sengaja tidak menyertakan Content-Length, sehingga bilah kemajuan unduhan dan proxy yang sensitif terhadap panjang tidak dapat melihat ukuran yang diketahui. Lebih baik gunakan download() / inline() yang buffered untuk respons kecil yang sensitif terhadap latensi.

Laravel: streamed download for a large report
$document = $this->documents->create();
// ... emit content onto $document ...
return PdfResponse::streamDownload($document, 'annual-report.pdf');
  • Dokumen baru per pemanggilan. Di ketiga integrasi, dokumen adalah hasil dari factory dan baru pada setiap resolusi. Jangan men-cache dokumen yang sudah di-resolve untuk dokumen logis yang berbeda, atau lintas request dalam worker yang berjalan lama. State konten yang usang akan ikut terbawa.
  • Nama berkas kosong. Nama berkas kosong yang diteruskan ke factory PdfResponse akan beralih ke nama default (document.pdf) alih-alih menghasilkan disposisi kosong. Berikan nama berkas yang eksplisit dan bermakna.
  • Nama berkas non-ASCII. Respons Laravel menambahkan parameter RFC 5987 filename*= secara otomatis untuk nama non-ASCII, dan nama ASCII menggunakan parameter biasa. Jangan meng-encode nama berkas sendiri secara manual.
  • Respons streaming di belakang proxy yang menyangga. Proxy yang menyangga seluruh body membatalkan manfaat memori dari streaming. Konfigurasikan proxy untuk men-stream respons PDF, atau gunakan respons buffered pada jalur tersebut.
  • Callback streaming Symfony. Varian streaming Symfony mengembalikan StreamedResponse dengan callback yang mengirimkan output. Jangan menulis ke body respons sendiri setelah mengembalikannya.

Pembuatan sinkron dalam controller memblokir request selama seluruh proses pembuatan PDF. Dokumen satu halaman biasanya masih jauh di bawah anggaran request yang umum. Untuk keluaran multi-halaman atau batch, pindahkan pembuatan dari thread request menggunakan job antrean — lihat Menghasilkan PDF dalam job antrean. Varian streaming mengurangi memori puncak untuk dokumen besar dengan konsekuensi Content-Length yang tidak diketahui. Pilih varian tersebut ketika memori menjadi kendala dan bilah kemajuan tidak diperlukan.

  • Factory PdfResponse menerapkan kumpulan header keamanan respons yang tetap dan membersihkan nama berkas unduhan di setiap integrasi. Jangan menambahkan header tersebut sendiri.
  • Jangan pernah menyisipkan input pengguna yang belum divalidasi langsung ke nama berkas yang Anda teruskan ke factory. Berikan nilai yang Anda kendalikan, dan biarkan factory membersihkannya sebagai lapisan kedua.
  • Dalam blok catch, catat kelas exception dan identifier korelasi, bukan pesan atau trace exception. Trace mentah dalam log sink merupakan kebocoran informasi.
  • Jangan pernah menulis blok catch yang kosong. Setiap contoh di sini mencatat dan mengembalikan respons error yang terdefinisi.

Halaman keamanan dan operasi setiap integrasi mendokumentasikan model ancaman integrasi tersebut: kumpulan header, aturan pembersihan nama berkas, dan masa berlaku pengikatan dokumen.

Panduan ini tidak membuat klaim standar normatif apa pun. Setiap pemanggilan API yang ditampilkan berasal dari permukaan publik terverifikasi pada integrasi yang disebutkan, dan sudah diperiksa silang dengan halaman quickstart serta penggunaan produksi tiap paket. Halaman penggunaan produksi upstream yang ditautkan di bagian Lihat juga mendokumentasikan semantik header dan perilaku pengikatan container yang menjadi sandaran integrasi, beserta sitasi PSR-nya. Halaman cookbook ini menyatakan kembali penggunaannya dan mengacu pada halaman-halaman tersebut untuk sitasi normatif.