계약 / 스트리밍
한눈에 보기
섹션 제목: “한눈에 보기”스트리밍 도메인은 두 개의 experimental 인터페이스를 포함합니다. 증분 PDF 출력을 위한 StreamingWriterInterface와 페이지 수준 콘텐츠 구성을 위한 CursorInterface입니다. Core는 두 인터페이스를 모두 구현하는 최종(final) 테스트된 엔진을 제공합니다. 엔진 클래스는 내부용이므로, 직접 구현하기보다는 공개 experimental 계약을 통해 동작을 사용합니다. 이 등급은 experimental이므로, 계약은 사전 지원 중단(deprecation) 공지와 함께 마이너 릴리스에서 변경될 수 있습니다. 프로덕션에서 이에 의존하기 전에 버전을 엄격하게 고정하거나 자체 어댑터로 감싸십시오.
composer require nextpdf/core:^3개념 개요
섹션 제목: “개념 개요”스트리밍 writer는 각 페이지가 구성되는 즉시 직렬화하며, 다음 페이지가 시작되기 전에 출력에 플러시할 수 있습니다. 이는 문서가 사용 가능한 메모리 예산을 초과하는 워크로드를 위해 설계된 경로입니다. 인메모리 writer는 문서 전체를 보유합니다. 스트리밍 writer는 그렇지 않습니다. StreamingWriterInterface는 엄격한 상태 머신을 정의합니다. 새 인스턴스는 CLOSED 상태입니다. open()은 인스턴스를 OPEN 상태로 전환하고 호출자가 제공한 스트림에 PDF 헤더를 씁니다. newPage()는 인스턴스를 PAGING 상태로 전환하고 cursor를 반환합니다. close()는 상호 참조 구조와 트레일러를 쓰고 인스턴스를 종료 상태인 CLOSED로 전환합니다. 상호 참조 스트림은 각 객체 번호를 해당 바이트 오프셋에 매핑합니다 — ISO 32000-2 §7. 인스턴스당 하나의 세션만 실행됩니다. close() 이후 인스턴스는 소진됩니다. 스트림 리소스의 소유자는 호출자입니다. writer는 스트림에 쓰기만 하며 절대 닫지 않습니다.
CursorInterface는 페이지 수준 쓰기 표면입니다. cursor는 StreamingWriterInterface::newPage()에서 얻으며, 확정(finalize)되거나 다음 newPage()가 자동으로 확정하거나 close()가 무효화할 때까지 유효합니다. 무효화는 영구적입니다. cursor는 재활성화할 수 없습니다. 무효화된 cursor의 모든 메서드는 LogicException을 던집니다. cursor는 원시 콘텐츠 스트림 연산자를 쓰고, 활성 글꼴을 설정하며, 위치가 지정된 텍스트를 씁니다. 콘텐츠 스트림은 페이지 콘텐츠를 그래픽 연산자의 시퀀스로 인코딩합니다 — ISO 32000-2 §8. cursor는 저수준 표면입니다. 텍스트 셰이핑, 양방향 재정렬, 줄 바꿈 또는 어떤 레이아웃도 수행하지 않습니다. 이러한 작업은 Document 수준의 관심사로 남습니다. 전반에 걸쳐 단일 cursor 불변식이 유지됩니다. 어느 시점이든 유효한 cursor는 최대 하나뿐입니다.
두 인터페이스 모두 experimental이며, Core는 그 뒤에서 동작하는 엔진을 제공합니다 — 최종(final) StreamingWriterInterface 구현, 해당 페이지 cursor, 그리고 메모리 벤치마킹에 사용되는 폐기(discard) 싱크입니다. 이러한 엔진 클래스는 내부용이며 공개 표면의 일부가 아닙니다. 스트리밍을 사용하는 지원되는 방법은 experimental 계약에 의존하고 Core가 구현을 제공하도록 하는 것입니다. 각 타입의 PHPDoc은 라이프사이클 상태 머신과 범위 근거에 대해 streaming-writer ADR을 가리킵니다. 이 등급은 experimental이므로, 계약 시그니처는 사전 지원 중단(deprecation) 공지와 함께 마이너 릴리스에서 여전히 변경될 수 있습니다. 프로덕션에서 이에 의존하기 전에 버전을 엄격하게 고정하거나 자체 어댑터로 감싸십시오.
API 표면
섹션 제목: “API 표면”| 타입 | 종류 | 주요 멤버 | 안정성 | 도입 버전 |
|---|---|---|---|---|
StreamingWriterInterface | interface | open(resource, Config), newPage(?PageSize): CursorInterface, close() | experimental(제공되는 엔진) | 3.1.0 |
CursorInterface | interface | writeContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage() | experimental(제공되는 엔진) | 3.1.0 |
open()은 쓰기 불가능한 스트림에 대해 InvalidArgumentException을, writer가 이미 열려 있는 경우 LogicException을 던집니다. close()는 멱등(idempotent)하지 않습니다. 이중 close는 예외를 던집니다.
코드 예제 — 빠른 시작
섹션 제목: “코드 예제 — 빠른 시작”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\StreamingWriterInterface;use NextPDF\Core\Config;
/** * Drive a streaming writer through one page. * * The parameter is the experimental contract; Core supplies the * implementation. Type-hint the interface and let the engine satisfy it. * * @param StreamingWriterInterface $writer A Core-supplied streaming writer. * @param resource $stream A writable, caller-owned stream. */function writeOnePage(StreamingWriterInterface $writer, $stream): void{ $writer->open($stream, new Config()); $cursor = $writer->newPage(); $cursor->setFont('helvetica', '', 12.0); $cursor->writeText(72.0, 720.0, 'Streamed page.'); $cursor->finalizePage(); $writer->close(); // The caller closes $stream after close() returns.}이 함수는 experimental 인터페이스를 기준으로 작성되었으므로 엔진 클래스와 분리된 상태로 유지됩니다. Core는 호출 지점에서 동작하는 구현을 주입합니다.
코드 예제 — 프로덕션
섹션 제목: “코드 예제 — 프로덕션”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\StreamingWriterInterface;use NextPDF\Core\Config;use NextPDF\ValueObjects\PageSize;use Psr\Log\LoggerInterface;
final readonly class LargeReportStreamer{ public function __construct( private StreamingWriterInterface $writer, private LoggerInterface $logger, ) {}
/** * Stream a multi-page report to a caller-owned file handle. * * @param resource $stream Writable file handle owned by the caller. * @param list<list<string>> $pages One list of text lines per page. */ public function stream($stream, array $pages): void { $this->writer->open($stream, new Config());
try { foreach ($pages as $lines) { $cursor = $this->writer->newPage(PageSize::A4()); $cursor->setFont('helvetica', '', 11.0);
$y = 760.0; foreach ($lines as $line) { $cursor->writeText(72.0, $y, $line); $y -= 14.0; }
$cursor->finalizePage(); } } finally { $this->writer->close(); } }}여기서 finally 블록은 페이지 루프가 예외를 던지더라도 writer가 닫히고 트레일러가 기록되도록 보장합니다. 호출자는 여전히 스트림을 소유하며 직접 닫습니다.
엣지 케이스 및 주의 사항
섹션 제목: “엣지 케이스 및 주의 사항”- 엔진 클래스가 아니라 인터페이스에 의존하십시오. 두 계약을 모두 구현하는 엔진은 내부용이며 공개 표면의 일부가 아닙니다. 이를
new로 생성하거나 이름으로 참조하지 마십시오.StreamingWriterInterface를 타입 힌트로 사용하고 Core가 구현을 제공하도록 하십시오. - 계약은
experimental입니다. 해당 시그니처는 사전 지원 중단(deprecation) 공지와 함께 마이너 릴리스에서 변경될 수 있습니다. 프로덕션에서 이에 의존하기 전에 버전을 엄격하게 고정하거나 자체 어댑터로 감싸십시오. - cursor는 다음
newPage()또는close()가 호출되는 순간 무효화됩니다. 오래된 cursor를 보유한 채 그 cursor의 메서드를 호출하면LogicException이 발생합니다. 명확성을 위해 명시적으로 확정(finalize)하십시오. close()는 멱등(idempotent)하지 않습니다. 이중 close는 복구 가능한 조건이 아니라 호출자의 버그입니다. 계약은 예외를 던집니다.- writer는 절대 스트림을 닫지 않습니다.
close()가 반환된 후 호출자가 소유한 핸들을 닫지 않으면 파일 디스크립터가 누수됩니다. - 엔진은 확정된 각 페이지를 플러시하므로 상주 메모리가 페이지 수에 따라 증가하지 않습니다. 정확한 메모리 프로필은
experimental등급에 속하는 사항이며 마이너 릴리스 간에 달라질 수 있습니다. 단일 측정값에서 나온 가정을 하드코딩하지 마십시오.
스트리밍 설계는 최대 메모리를 제한합니다. 제공되는 엔진은 완료된 각 페이지를 플러시하고 해당 버퍼를 해제하므로, 인메모리 writer와 달리 상주 집합이 페이지 수에 따라 증가하지 않습니다. 엔진은 프로세스 풋프린트를 거의 일정하게 유지하기 위해 상호 참조 및 페이지 트리 관리 정보를 디스크 기반 임시 스트림으로 스필합니다. 구체적인 메모리 및 실행 시간 수치는 experimental 등급에 속하는 사항이며 마이너 릴리스 간에 달라질 수 있으므로, 여기서는 고정된 수치를 단언하지 않습니다. 실행 시간 1500 ms와 피크 64 MB의 performance_budget은 캔버스 한도일 뿐 계약상 보장이 아닙니다. 재현성은 bitwise입니다. 동일한 콘텐츠와 구성은 바이트 단위로 동일한 출력을 생성하며, 이를 엔진의 골든 베이스라인 테스트가 고정합니다.
보안 참고 사항
섹션 제목: “보안 참고 사항”cursor의 writeContent()는 저수준 이스케이프 해치입니다. 이 메서드는 제공된 바이트를 페이지 콘텐츠 스트림에 그대로 추가하며 연산자 구문이나 의미를 검증하지 않습니다. writeContent()에 전달된 신뢰할 수 없는 입력은 손상되거나 악의적인 PDF를 생성합니다. 호출자는 해당 메서드를 신뢰된 입력 전용 표면으로 취급해야 하며, 호출자의 영향을 받을 수 있는 텍스트에는 writeText()를 우선 사용해야 합니다. 제공되는 cursor는 writeText()에 전달된 텍스트를 PDF 리터럴 문자열 문법에 맞게 이스케이프하지만, 원시 연산자는 정제(sanitise)하지 않습니다. 호출자 소유 스트림 모델도 보안 속성입니다. 엔진은 스트림에 쓰지만 절대 닫거나 다시 열지 않으므로, 출력을 리디렉션할 수 없습니다. 엔진이 제공되는 만큼 런타임 공격 표면은 실제로 존재합니다. 책임은 호출자가 신뢰할 수 없는 바이트를 절대 writeContent()에 공급하지 않는 것과, 엔진이 계약의 불변식을 준수하는 것에 있습니다.
적합성
섹션 제목: “적합성”| 주장 | 표준 | 절 | 근거 |
|---|---|---|---|
| 콘텐츠 스트림은 페이지 콘텐츠를 그래픽 연산자의 시퀀스로 인코딩하며, cursor 가 이를 추가합니다. | ISO 32000-2 | §8 | |
| writer 는 close 시점에 각 객체 번호를 해당 바이트 오프셋에 매핑하는 상호 참조 구조를 내보냅니다. | ISO 32000-2 | §7 |
두 절 모두 용어집에 고정되어 있으며 의역되었습니다. NextPDF는 어떠한 규범 텍스트도 재현하지 않습니다. 계약 PHPDoc이 참조하는 streaming-writer ADR이 라이프사이클과 범위 근거를 담고 있습니다.
상업적 맥락
섹션 제목: “상업적 맥락”테스트된 스트리밍 엔진은 이러한 experimental 계약 뒤에서 오픈 소스 Core에 제공됩니다. 엔진 클래스는 내부용이므로, 구체 클래스 이름 대신 공개 계약을 통해 스트리밍을 사용합니다. NextPDF Pro와 NextPDF Enterprise는 동일한 계약을 따르므로, Core에서 StreamingWriterInterface를 기준으로 작성된 코드는 동일한 계약의 Premium 구현에 대해서도 유효하게 유지됩니다. 유의해야 할 사항은 에디션이나 제공 여부가 아니라 experimental 등급입니다. 시그니처는 사전 지원 중단(deprecation) 공지와 함께 마이너 릴리스에서 변경될 수 있습니다.
관련 항목
섹션 제목: “관련 항목”- 계약: 41개 공개 인터페이스(SPI) — SPI 개요 및 안정성 등급.
- 계약 / 문서 — 이러한 계약을 보완하는 인메모리 writer.
- Writer — PDF 객체 및 상호 참조 이미터.
- HTML / 스트리밍 제약(ADR-001) — 스트리밍 범위 근거.
- 성능 — 스트리밍 출력의 메모리 제한 근거.