콘텐츠로 이동

컨트롤러에서 생성한 PDF 반환하기

컨트롤러 액션 안에서 PDF를 생성하고 HTTP 응답으로 반환합니다. 각 프레임워크 통합은 해당 프레임워크의 응답 객체를 빌드하는 PdfResponse 헬퍼를 제공합니다. 이 헬퍼는 Content-Type: application/pdf를 설정하고, 보안 헤더를 첨부하며, 파일 이름을 정제합니다. 이 가이드는 Laravel, Symfony, CodeIgniter 4에 대한 세 가지 전달 모드(인라인 미리 보기, 파일 다운로드, 스트리밍 전달)를 다룹니다.

작업 도중 예상치 못한 문제가 생기지 않도록 다음 사전 요구 사항을 먼저 확인하세요:

  • NextPDF 코어가 설치되어 있습니다.
  • 프레임워크 통합 중 하나가 설치되어 있고, 해당 서비스 프로바이더, 번들 또는 서비스가 자동 탐색됩니다. 시작하기 전에 사용 중인 프레임워크의 설치 페이지에서 탐색 여부를 확인하세요.
  • 스트리밍 모드에는 추가 패키지가 필요하지 않습니다. 모든 통합은 버퍼링 방식과 스트리밍 방식을 모두 제공합니다.

이 문서는 방법 안내서이며, 사용 중인 프레임워크에서 요청을 컨트롤러로 라우팅하는 방법을 이미 알고 있다고 가정합니다. 프레임워크별로 처음 실행해 볼 수 있는 예제는 참고 항목에 링크된 프레임워크 빠른 시작을 읽어 보세요.

사용 중인 프레임워크에 맞는 통합을 설치하세요. 다음 중 하나를 실행합니다.

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는 Flex를 통해 번들을 자동으로 등록하고, CodeIgniter는 서비스를 자동 탐색합니다. 계속하기 전에 사용 중인 프레임워크의 설치 페이지에서 탐색 여부를 확인하세요.

모든 프레임워크 통합은 동일한 세 부분 구조를 공유합니다. 새 문서를 얻는 방법, 그 문서에 콘텐츠를 써 넣는 호출 집합, 완성된 문서를 HTTP 응답으로 변환하는 PdfResponse 팩토리입니다. 문서 API(addPage(), cell(), setFont())는 코어 엔진의 API 표면이며 프레임워크 전반에서 동일합니다. 각 프레임워크에는 고유한 HTTP 응답 유형이 있으므로, 응답 팩토리는 반환하는 응답 클래스에서만 차이가 있습니다.

PdfResponse는 세 가지 전달 모드를 제공합니다. 인라인Content-Disposition: inline 헤더를 설정해 브라우저가 뷰어 탭에서 PDF를 렌더링하게 합니다. 다운로드Content-Disposition: attachment를 설정해 브라우저가 파일을 저장하게 합니다. 스트리밍은 전체 문서를 메모리에 버퍼링하는 대신 PDF 본문을 고정 크기 청크로 내보냅니다. 알려진 Content-Length보다 최대 메모리 사용량이 더 중요한 대용량 문서에는 이 모드를 선택하세요.

프레임워크에서 관례적으로 쓰는 해석 경로로 문서를 얻으세요:

  • Laravel — 컨테이너에서 NextPDF\Contracts\DocumentFactoryInterfaceapp(...)로 해석하고 create()를 호출하면 새 NextPDF\Core\Document가 반환됩니다. 이 타입은 PdfResponse 팩토리가 받아들이는 구체 타입입니다.
  • SymfonyNextPDF\Symfony\Service\PdfFactory를 주입하고 create()를 호출하면 구성된 문서 기본값이 이미 적용된 새 NextPDF\Core\Document가 반환됩니다.
  • CodeIgniter 4Pdf 라이브러리를 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

Laravel PdfResponseNextPDF\Laravel\Http\PdfResponse에, Symfony용은 NextPDF\Symfony\Http\PdfResponse에, CodeIgniter용은 NextPDF\CodeIgniter\Http\PdfResponse에 있습니다. 각 통합의 보안 및 운영 페이지는 패키지별 전체 응답 동작(헤더 집합, 디스포지션 규칙, 파일 이름 정제)을 문서화합니다. 해당 페이지는 참고 항목에 링크되어 있습니다.

다음은 각 프레임워크에서 가장 작은 다운로드 액션입니다. 문서 호출은 동일한 코어 표면을 사용하며, 컨트롤러 스캐폴딩만 다릅니다.

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

브라우저에서 미리 보려면 다운로드 대신 Laravel과 Symfony에서는 download(...) 호출을 inline(...)로, CodeIgniter에서는 $pdf->inline('report.pdf')로 바꾸세요. 디스포지션은 inline이 되고, 나머지 헤더는 모두 동일하게 유지됩니다.

프로덕션 액션은 의존성을 주입하고, 통합에서 문서화한 가장 구체적인 예외를 잡으며, 트레이스를 유출하지 않고 실패 클래스를 로깅하고, 정의된 HTTP 오류를 반환해야 합니다. 아래 예제는 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가 반환되며, 이 타입은 Laravel PdfResponse 팩토리가 받아들이는 구체 타입입니다. 요청마다 새 문서를 해석하면 테스트에서 팩토리를 계속 교체 가능한 상태로 둘 수 있습니다. 단일 장기 실행 워커 프로세스 내에서 서로 무관한 두 문서에 하나의 컨트롤러 인스턴스를 재사용하지 마세요.

매우 큰 문서에서는 최대 메모리를 제한하기 위해 버퍼링 팩토리를 스트리밍 팩토리로 교체하세요. 스트리밍 방식은 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');
  • 호출마다 새 문서. 세 가지 통합 모두에서 문서는 팩토리를 통해 생성되며, 해석할 때마다 새로 만들어집니다. 해석된 문서를 여러 논리적 문서 사이에서, 또는 장기 실행 워커의 여러 요청 사이에서 캐시하지 마세요. 오래된 콘텐츠 상태가 이월됩니다.
  • 빈 파일 이름. PdfResponse 팩토리에 전달된 빈 파일 이름은 빈 디스포지션을 생성하는 대신 기본 이름(document.pdf)으로 대체됩니다. 명시적이고 의미 있는 파일 이름을 전달하세요.
  • 비 ASCII 파일 이름. Laravel 응답은 비 ASCII 이름에 대해 RFC 5987 filename*= 매개변수를 자동으로 추가하며, ASCII 이름에는 일반 매개변수를 사용합니다. 파일 이름을 직접 인코딩하지 마세요.
  • 버퍼링 프록시 뒤의 스트리밍 응답. 전체 본문을 버퍼링하는 프록시는 스트리밍의 메모리 이점을 무효화합니다. 프록시가 PDF 응답을 스트리밍하도록 구성하거나, 해당 경로에서는 버퍼링 응답을 사용하세요.
  • Symfony 스트리밍 콜백. Symfony 스트리밍 방식은 콜백이 출력을 플러시하는 StreamedResponse를 반환합니다. 응답을 반환한 후 응답 본문에 직접 쓰지 마세요.

컨트롤러 내부에서 동기적으로 생성하면 전체 PDF 빌드 동안 요청이 차단됩니다. 단일 페이지 문서라면 일반적인 요청 예산 안에 충분히 들어갑니다. 다중 페이지 또는 배치 출력에서는 큐 작업으로 생성을 요청 스레드에서 분리하세요 — 큐 작업에서 PDF 생성하기를 참고하세요. 스트리밍 방식은 알 수 없는 Content-Length를 감수하는 대신 대용량 문서의 최대 메모리를 줄입니다. 메모리 제약이 있고 진행 표시줄이 필요하지 않을 때 이를 선택하세요.

  • 모든 통합에서 PdfResponse 팩토리는 고정된 응답 강화 헤더 집합을 적용하고 다운로드 파일 이름을 정제합니다. 해당 헤더를 직접 추가하지 마세요.
  • 검증되지 않은 사용자 입력을 팩토리에 전달하는 파일 이름에 직접 삽입하지 마세요. 통제 가능한 값을 전달하고, 팩토리가 두 번째 계층에서 이를 정제하도록 하세요.
  • catch 블록에서는 예외 메시지나 트레이스가 아니라 예외 클래스와 상관관계 식별자를 로깅하세요. 로그 싱크의 원시 트레이스는 정보 유출입니다.
  • catch 블록을 절대 작성하지 마세요. 여기의 각 예제는 로깅하고 정의된 오류 응답을 반환합니다.

각 통합의 보안 및 운영 페이지는 통합별 위협 모델(헤더 집합, 파일 이름 정제 규칙, 문서 바인딩 수명)을 문서화합니다.

이 가이드는 규범적 표준 주장을 하지 않습니다. 여기에 표시된 모든 API 호출은 명시된 통합의 검증된 공개 API 표면이며, 각 패키지의 빠른 시작 및 프로덕션 사용 페이지와 대조 확인되었습니다. 참고 항목에 링크된 상위 프로덕션 사용 페이지는 통합이 의존하는 헤더 시맨틱과 컨테이너 바인딩 동작을 PSR 인용과 함께 문서화합니다. 이 쿡북 페이지는 사용법만 다시 설명하며 규범적 인용은 해당 페이지에 위임합니다.