ข้ามไปยังเนื้อหา

ส่งคืน PDF ที่สร้างจากคอนโทรลเลอร์

สร้างไฟล์ Portable Document Format (PDF) ภายในแอ็กชันของคอนโทรลเลอร์ แล้วส่งคืนในรูปแบบการตอบสนอง Hypertext Transfer Protocol (HTTP) การรวมของแต่ละเฟรมเวิร์กมีตัวช่วย PdfResponse สำหรับสร้างอ็อบเจกต์การตอบสนอง กำหนด Content-Type: application/pdf แนบเฮดเดอร์ความปลอดภัย และล้างชื่อไฟล์ให้ปลอดภัย คู่มือนี้ครอบคลุมโหมดการส่งมอบสามแบบ ได้แก่ การแสดงตัวอย่างแบบอินไลน์ การดาวน์โหลดไฟล์ และการส่งมอบแบบสตรีม สำหรับ Laravel, Symfony และ CodeIgniter 4

ตรวจสอบข้อกำหนดเบื้องต้นเหล่านี้ เพื่อให้เส้นทางของคอนโทรลเลอร์พร้อมใช้งานก่อนเริ่มต้น:

  • ติดตั้ง core ของ NextPDF แล้ว
  • ติดตั้งการรวมเฟรมเวิร์กหนึ่งรายการแล้ว และระบบค้นพบ service provider, bundle หรือ service ของการรวมนั้นแล้ว ตรวจสอบการค้นพบในหน้าการติดตั้งสำหรับเฟรมเวิร์กของคุณก่อนเริ่มต้น
  • โหมดสตรีมไม่ต้องใช้แพ็กเกจเพิ่มเติม การรวมทั้งหมดมีตัวแปรแบบสตรีมควบคู่กับตัวแปรแบบบัฟเฟอร์

หน้านี้เป็นคู่มือวิธีทำ โดยถือว่าคุณทราบวิธีกำหนดเส้นทางคำขอไปยังคอนโทรลเลอร์ในเฟรมเวิร์กของคุณอยู่แล้ว สำหรับตัวอย่างแรกที่รันได้ในแต่ละเฟรมเวิร์ก โปรดอ่าน quickstart ของเฟรมเวิร์กที่ลิงก์ไว้ในส่วนดูเพิ่มเติม

ติดตั้งการรวมสำหรับเฟรมเวิร์กของคุณด้วยคำสั่งใดคำสั่งหนึ่งต่อไปนี้

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

สำหรับ Laravel ให้เผยแพร่การกำหนดค่าหลังติดตั้ง

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

Symfony จะลงทะเบียน bundle ผ่าน Flex ส่วน CodeIgniter จะค้นพบ service โดยอัตโนมัติ ยืนยันการค้นพบในหน้าการติดตั้งของเฟรมเวิร์กของคุณก่อนดำเนินการต่อ

การรวมของทุกเฟรมเวิร์กใช้รูปแบบสามขั้นตอนเดียวกัน: สร้างเอกสารใหม่ เขียนเนื้อหาลงในเอกสารนั้น แล้วส่งต่อไปยังแฟกทอรี PdfResponse ที่ส่งคืนการตอบสนอง HTTP API ของเอกสาร (addPage(), cell(), setFont()) คือพื้นผิวของเอนจิน core และเหมือนกันในทุกเฟรมเวิร์ก แฟกทอรีการตอบสนองต่างกันเฉพาะคลาสการตอบสนองที่ส่งคืน เพราะแต่ละเฟรมเวิร์กมีชนิดการตอบสนอง HTTP ของตนเอง

PdfResponse มีโหมดการส่งมอบสามแบบ ได้แก่ อินไลน์ ซึ่งกำหนดเฮดเดอร์ Content-Disposition: inline เพื่อให้เบราว์เซอร์เรนเดอร์ PDF ในแท็บตัวแสดงเอกสาร; ดาวน์โหลด ซึ่งกำหนด Content-Disposition: attachment เพื่อให้เบราว์เซอร์บันทึกไฟล์; และ สตรีม ซึ่งปล่อยเนื้อหา PDF เป็นชิ้นที่มีขนาดคงที่ แทนการบัฟเฟอร์เอกสารทั้งฉบับไว้ในหน่วยความจำ เลือกโหมดนี้สำหรับเอกสารขนาดใหญ่เมื่อหน่วยความจำสูงสุดสำคัญกว่าการทราบ Content-Length ที่แน่นอน

รับเอกสารผ่านกลไกการรีโซลฟ์ตามปกติของเฟรมเวิร์กของคุณ:

  • Laravel — รีโซลฟ์ NextPDF\Contracts\DocumentFactoryInterface จากคอนเทนเนอร์ด้วย app(...) แล้วเรียก create() ซึ่งส่งคืน NextPDF\Core\Document ใหม่ — ชนิดที่เป็นรูปธรรมซึ่งแฟกทอรี PdfResponse รับ
  • Symfony — ฉีด NextPDF\Symfony\Service\PdfFactory แล้วเรียก create() ซึ่งส่งคืน NextPDF\Core\Document ใหม่พร้อมค่าเริ่มต้นของเอกสารที่กำหนดค่าไว้แล้ว
  • CodeIgniter 4 — รีโซลฟ์ไลบรารี Pdf ผ่าน Services::pdf() (หรือตัวช่วย pdf()) หรือรับเอกสารเปล่าผ่าน pdf_document() ได้
ประเด็นLaravelSymfonyCodeIgniter 4
เอกสารใหม่app(DocumentFactoryInterface::class)->create()PdfFactory::create()pdf_document() / Services::pdf()->document()
การตอบสนองแบบอินไลน์PdfResponse::inline($doc, $name)PdfResponse::inline($doc, $name)$pdf->inline($name) / PdfResponse::inline($doc, $name)
การตอบสนองแบบดาวน์โหลดPdfResponse::download($doc, $name)PdfResponse::download($doc, $name)$pdf->download($name) / PdfResponse::download($doc, $name)
อินไลน์แบบสตรีมPdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)
ดาวน์โหลดแบบสตรีมPdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)
ชนิดที่ส่งคืนIlluminate\Http\Response (สตรีม: StreamedResponse)Symfony\Component\HttpFoundation\Response (สตรีม: StreamedResponse)CodeIgniter\HTTP\DownloadResponse

คลาส PdfResponse ของ Laravel อยู่ที่ NextPDF\Laravel\Http\PdfResponse ของ Symfony อยู่ที่ NextPDF\Symfony\Http\PdfResponse และของ CodeIgniter อยู่ที่ NextPDF\CodeIgniter\Http\PdfResponse หน้าความปลอดภัยและการปฏิบัติการของการรวมแต่ละรายการบันทึกพฤติกรรมการตอบสนองทั้งหมดของแพ็กเกจนั้น ได้แก่ ชุดเฮดเดอร์ กฎ disposition และการล้างชื่อไฟล์ให้ปลอดภัย ลิงก์ของหน้าเหล่านั้นอยู่ในส่วนดูเพิ่มเติม

ตัวอย่างต่อไปนี้คือแอ็กชันการดาวน์โหลดขั้นต่ำในแต่ละเฟรมเวิร์ก การเรียกใช้งานเอกสารใช้พื้นผิว core เดียวกัน เปลี่ยนเฉพาะโครงร่างของคอนโทรลเลอร์เท่านั้น

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

หากต้องการแสดงตัวอย่างในเบราว์เซอร์แทนการดาวน์โหลด ให้สลับการเรียก download(...) เป็น inline(...) ใน Laravel และ Symfony หรือ $pdf->inline('report.pdf') ใน CodeIgniter disposition จะเปลี่ยนเป็น inline และเฮดเดอร์อื่นทั้งหมดจะเหมือนเดิม

แอ็กชันสำหรับการใช้งานจริงควรรับการพึ่งพาผ่าน injection จับ exception ที่เฉพาะเจาะจงที่สุดตามที่การรวมระบุไว้ในเอกสาร บันทึกคลาสของ exception โดยไม่เปิดเผย trace และส่งคืนข้อผิดพลาด HTTP ที่กำหนดไว้ ตัวอย่างด้านล่างใช้ constructor injection ของ Laravel รูปแบบที่เทียบเท่าสำหรับ Symfony และ CodeIgniter ใช้แนวทางเดียวกันและอยู่ในหน้าการใช้งานจริงของการรวมแต่ละรายการ

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

ฉีด DocumentFactoryInterface และเรียก create() ในแต่ละแอ็กชัน การเรียกนี้ส่งคืน NextPDF\Core\Document ใหม่ — ชนิดที่เป็นรูปธรรมซึ่งแฟกทอรี PdfResponse ของ Laravel รับ การรีโซลฟ์เอกสารใหม่ต่อหนึ่งคำขอทำให้สามารถสลับแฟกทอรีในการทดสอบได้ อย่านำอินสแตนซ์คอนโทรลเลอร์เดียวกันมาใช้ซ้ำกับเอกสารสองฉบับที่ไม่เกี่ยวข้องกันภายในกระบวนการ worker ที่ทำงานต่อเนื่องเป็นเวลานานหนึ่งกระบวนการ

สำหรับเอกสารขนาดใหญ่มาก ให้แทนที่แฟกทอรีแบบบัฟเฟอร์ด้วยแบบสตรีมเพื่อจำกัดหน่วยความจำสูงสุด ตัวแปรแบบสตรีมส่งคืน StreamedResponse (Laravel และ Symfony) และปล่อยเนื้อหาเป็นชิ้นที่มีขนาดคงที่ ตัวแปรนี้จงใจละเว้น Content-Length ดังนั้นแถบความคืบหน้าการดาวน์โหลดและพร็อกซีที่อาศัยความยาวจะไม่เห็นขนาดที่ทราบ ควรใช้ download() / inline() แบบบัฟเฟอร์สำหรับการตอบสนองขนาดเล็กที่ไวต่อเวลาแฝง

Laravel: streamed download for a large report
$document = $this->documents->create();
// ... emit content onto $document ...
return PdfResponse::streamDownload($document, 'annual-report.pdf');
  • เอกสารใหม่ต่อการเรียกหนึ่งครั้ง ในการรวมทั้งสามรายการ เอกสารถูกสร้างจากแฟกทอรี ซึ่งสร้างใหม่ในการรีโซลฟ์แต่ละครั้ง อย่าแคชเอกสารที่รีโซลฟ์แล้วเพื่อใช้ข้ามเอกสารเชิงตรรกะ หรือข้ามคำขอใน worker ที่ทำงานต่อเนื่องเป็นเวลานาน สถานะเนื้อหาเก่าจะตกค้างต่อไป
  • ชื่อไฟล์ว่าง ชื่อไฟล์ว่างที่ส่งไปยังแฟกทอรี PdfResponse จะย้อนกลับไปใช้ชื่อเริ่มต้น (document.pdf) แทนที่จะสร้าง disposition ว่างเปล่า ส่งชื่อไฟล์ที่ชัดเจนและมีความหมาย
  • ชื่อไฟล์ที่ไม่ใช่ ASCII การตอบสนองของ Laravel จะเพิ่มพารามิเตอร์ RFC 5987 filename*= โดยอัตโนมัติสำหรับชื่อที่ไม่ใช่ ASCII ส่วนชื่อที่เป็น ASCII จะใช้พารามิเตอร์ธรรมดา อย่าเข้ารหัสชื่อไฟล์ด้วยตนเอง
  • การตอบสนองแบบสตรีมที่อยู่หลังพร็อกซีที่บัฟเฟอร์ พร็อกซีที่บัฟเฟอร์เนื้อหาทั้งหมดจะลบล้างประโยชน์ด้านหน่วยความจำของการสตรีม กำหนดค่าพร็อกซีให้สตรีมการตอบสนอง PDF หรือใช้การตอบสนองแบบบัฟเฟอร์บนเส้นทางนั้น
  • คอลแบ็กแบบสตรีมของ Symfony ตัวแปรแบบสตรีมของ Symfony ส่งคืน StreamedResponse โดยคอลแบ็กของตัวแปรนี้จะ flush เอาต์พุต อย่าเขียนลงในเนื้อหาการตอบสนองด้วยตนเองหลังจากส่งคืนแล้ว

การสร้างแบบซิงโครนัสภายในคอนโทรลเลอร์จะบล็อกคำขอตลอดการสร้าง PDF ทั้งหมด เอกสารหน้าเดียวมักอยู่ภายในงบประมาณคำขอทั่วไปได้อย่างสบาย สำหรับเอาต์พุตหลายหน้าหรือแบบกลุ่ม ให้ย้ายการสร้างออกจากเธรดของคำขอด้วยงานที่จัดคิว — ดู สร้าง PDF ในงานที่จัดคิว ตัวแปรแบบสตรีมลดหน่วยความจำสูงสุดสำหรับเอกสารขนาดใหญ่ โดยแลกกับการที่ไม่ทราบ Content-Length เลือกใช้ตัวแปรเหล่านี้เมื่อหน่วยความจำเป็นข้อจำกัดและไม่ต้องการแถบความคืบหน้า

  • แฟกทอรี PdfResponse ใช้ชุดเฮดเดอร์ที่เพิ่มความแข็งแกร่งให้การตอบสนองซึ่งกำหนดไว้ตายตัว และล้างชื่อไฟล์สำหรับดาวน์โหลดให้ปลอดภัยในการรวมทั้งหมด อย่าเพิ่มเฮดเดอร์เหล่านั้นด้วยตนเอง
  • อย่าแทรกอินพุตของผู้ใช้ที่ไม่ผ่านการตรวจสอบลงในชื่อไฟล์ที่คุณส่งไปยังแฟกทอรีโดยตรงเด็ดขาด ส่งค่าที่คุณควบคุมได้ และให้แฟกทอรีล้างค่านั้นให้ปลอดภัยเป็นชั้นที่สอง
  • ในบล็อก catch ให้บันทึกคลาสของ exception และตัวระบุสำหรับเชื่อมโยง ไม่ใช่ข้อความหรือ trace ของ exception trace ดิบในแหล่งบันทึกถือเป็นการรั่วไหลของข้อมูล
  • อย่าเขียนบล็อก catch ที่ว่างเปล่าเด็ดขาด ตัวอย่างทุกตัวอย่างที่นี่จะบันทึกและส่งคืนการตอบสนองข้อผิดพลาดที่กำหนดไว้

หน้าความปลอดภัยและการปฏิบัติการของการรวมแต่ละรายการบันทึกแบบจำลองภัยคุกคามของการรวมนั้น ได้แก่ ชุดเฮดเดอร์ กฎการล้างชื่อไฟล์ให้ปลอดภัย และอายุของการผูกเอกสาร

คู่มือนี้ไม่ได้กล่าวอ้างเชิงบรรทัดฐานต่อมาตรฐานใด การเรียก API ทุกตัวที่แสดงคือพื้นผิวสาธารณะที่ตรวจสอบแล้วของการรวมที่ระบุไว้ ซึ่งตรวจสอบเทียบกับหน้า quickstart และหน้าการใช้งานจริงของแต่ละแพ็กเกจ หน้าการใช้งานจริงต้นทางที่ลิงก์ไว้ในส่วนดูเพิ่มเติมบันทึกความหมายของเฮดเดอร์และพฤติกรรมการผูกคอนเทนเนอร์ที่การรวมพึ่งพา พร้อมด้วยการอ้างอิง PSR หน้า cookbook นี้ทวนการใช้งานและส่งต่อการอ้างอิงเชิงบรรทัดฐานไปยังหน้าเหล่านั้น