Contracts / Streaming
Visão geral
Seção intitulada “Visão geral”O domínio de streaming inclui duas interfaces experimental: StreamingWriterInterface, para saída incremental de PDF, e CursorInterface, para composição de conteúdo em nível de página. O Core entrega um motor final e testado que implementa ambas. As classes do motor são internas, portanto você usa o contrato público experimental em vez de implementar o motor por conta própria. Como o nível é experimental, o contrato pode mudar em uma versão menor, com aviso prévio de descontinuação. Fixe a versão de forma rígida ou encapsule-o atrás do seu próprio adaptador antes de depender dele em produção.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3Visão conceitual
Seção intitulada “Visão conceitual”Um escritor de streaming serializa cada página à medida que você a compõe e pode descarregá-la na saída antes do início da próxima página. Use-o quando um documento puder exceder o orçamento de memória disponível. O escritor em memória mantém o documento inteiro; um escritor de streaming, não. StreamingWriterInterface define uma máquina de estados estrita. Uma nova instância está CLOSED. open() a move para OPEN e escreve o cabeçalho do PDF em um stream fornecido pelo chamador. newPage() a move para PAGING e retorna um cursor. close() escreve a estrutura de referência cruzada e o trailer; depois, move a instância para um CLOSED terminal. Um stream de referência cruzada mapeia cada número de objeto para o seu deslocamento em bytes, conforme abordado pela ISO 32000-2 §7. Apenas uma sessão é executada por instância. Após close(), a instância fica esgotada. O chamador é dono do recurso de stream. O escritor grava nele, mas nunca o fecha.
CursorInterface é a superfície de escrita em nível de página. Você obtém um cursor de StreamingWriterInterface::newPage(), e ele permanece válido até que você o finalize, até que o próximo newPage() o finalize automaticamente ou até que close() o invalide. A invalidação é permanente. Um cursor não pode ser reativado. Todo método chamado em um cursor invalidado lança LogicException. O cursor escreve operadores brutos de content stream, define a fonte ativa e escreve texto posicionado. Um content stream codifica o conteúdo da página como uma sequência de operadores gráficos, conforme abordado pela ISO 32000-2 §8. O cursor é uma superfície de baixo nível: ele não realiza shaping de texto, reordenação bidirecional, quebra de linha nem qualquer layout. Essas responsabilidades permanecem no nível de Document. O invariante de cursor único se mantém o tempo todo: no máximo um cursor é válido a qualquer momento.
As duas interfaces são experimental, e o Core entrega um motor funcional por trás delas: uma implementação final de StreamingWriterInterface, seu cursor de página e um sink de descarte usado para benchmarking de memória. Essas classes do motor são internas e não fazem parte da superfície pública. Para usar streaming, dependa do contrato experimental e deixe o Core fornecer a implementação. O PHPDoc de cada tipo aponta para o ADR do streaming-writer, que descreve a máquina de estados do ciclo de vida e a justificativa de escopo. Como o nível é experimental, a assinatura do contrato ainda pode mudar em uma versão menor, com aviso prévio de descontinuação. Fixe a versão de forma rígida ou encapsule-o atrás do seu próprio adaptador antes de depender dele em produção.
Superfície da API
Seção intitulada “Superfície da API”| Tipo | Espécie | Membros principais | Estabilidade | Desde |
|---|---|---|---|---|
StreamingWriterInterface | interface | open(resource, Config), newPage(?PageSize): CursorInterface, close() | experimental (motor entregue) | 3.1.0 |
CursorInterface | interface | writeContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage() | experimental (motor entregue) | 3.1.0 |
open() lança InvalidArgumentException para um stream que não permite escrita e LogicException se o escritor já estiver aberto. close() não é idempotente. Chamá-lo duas vezes lança uma exceção.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”<?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.}A função tem como alvo a interface experimental, portanto permanece desacoplada da classe do motor. O Core injeta uma implementação funcional no ponto de chamada.
Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”<?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(); } }}O bloco finally garante que o escritor seja fechado e que o trailer seja escrito, mesmo quando um loop de páginas lança uma exceção. O chamador continua sendo dono do stream e o fecha.
Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”- Dependa da interface, não da classe do motor. O motor que implementa os dois contratos é interno e não faz parte da superfície pública. Não use
newnessa classe nem a referencie pelo nome. Use a dica de tipoStreamingWriterInterfacee deixe o Core fornecer a implementação. - O contrato é
experimental. Sua assinatura pode mudar em uma versão menor, com aviso prévio de descontinuação. Fixe a versão de forma rígida ou encapsule-o atrás do seu próprio adaptador antes de depender dele em produção. - Um cursor se torna inválido no momento em que o próximo
newPage()ouclose()é chamado. Manter um cursor obsoleto e chamar um método nele lançaLogicException. Finalize explicitamente para maior clareza. close()não é idempotente. Chamá-lo duas vezes é um bug do chamador, não uma condição recuperável. O contrato lança uma exceção.- O escritor nunca fecha o stream. Se você esquecer de fechar um handle de propriedade do chamador após o retorno de
close(), você vaza um descritor de arquivo. - O motor descarrega cada página finalizada para que a memória residente não cresça com a quantidade de páginas. O perfil exato de memória é uma propriedade de nível
experimentale pode variar entre versões menores. Não fixe no código uma suposição baseada em uma única medição.
Desempenho
Seção intitulada “Desempenho”O design de streaming limita o pico de memória. O motor entregue descarrega cada página concluída e libera seu buffer, portanto o conjunto residente não cresce com a quantidade de páginas, ao contrário do escritor em memória. O motor despeja a contabilidade de referência cruzada e da árvore de páginas em streams temporários apoiados em disco para manter a pegada do processo quase constante. Números concretos de memória e de tempo decorrido são uma propriedade de nível experimental e podem mudar entre versões menores, portanto esta página não afirma nenhum número fixo. O performance_budget de 1500 ms de tempo decorrido e 64 MB de pico é o envelope de referência, não uma garantia contratual. A reprodutibilidade é bitwise: o mesmo conteúdo e a mesma configuração produzem uma saída idêntica byte a byte, como fixado pelos testes de baseline golden do motor.
Notas de segurança
Seção intitulada “Notas de segurança”O método writeContent() do cursor é uma saída de emergência de baixo nível. Ele anexa literalmente os bytes fornecidos ao content stream da página e não valida a sintaxe nem a semântica dos operadores. Entradas não confiáveis passadas para writeContent() produzem um PDF corrompido ou malicioso. Trate esse método como uma superfície destinada apenas a entradas confiáveis e prefira writeText() para qualquer texto influenciado pelo chamador. O cursor entregue faz escape do texto passado para writeText() conforme a gramática de strings literais do PDF, mas não sanitiza operadores brutos. O modelo de stream de propriedade do chamador também é uma propriedade de segurança. O motor grava no stream, mas nunca o fecha nem o reabre, portanto não pode redirecionar a saída. A superfície de ataque em tempo de execução é real porque o motor é entregue. Os chamadores nunca devem alimentar writeContent() com bytes não confiáveis, e o motor deve honrar os invariantes do contrato.
Conformidade
Seção intitulada “Conformidade”| Afirmação | Norma | Cláusula | Evidência |
|---|---|---|---|
| Um content stream codifica o conteúdo da página como uma sequência de operadores gráficos, que o cursor anexa. | ISO 32000-2 | §8 | |
| O escritor emite uma estrutura de referência cruzada que mapeia cada número de objeto para o seu deslocamento em bytes no fechamento. | ISO 32000-2 | §7 |
As duas cláusulas são fixadas no glossário e parafraseadas. O NextPDF não reproduz nenhum texto normativo. O ADR do streaming-writer referenciado pelo PHPDoc do contrato contém a justificativa do ciclo de vida e do escopo.
Contexto comercial
Seção intitulada “Contexto comercial”Um motor de streaming testado é entregue no Core de código aberto por trás destes contratos experimental. As classes do motor são internas, portanto você usa streaming por meio do contrato público em vez de um nome de classe concreto. O NextPDF Pro e o NextPDF Enterprise seguem o mesmo contrato, portanto o código escrito contra StreamingWriterInterface no Core permanece válido contra uma implementação Premium do mesmo contrato. A ressalva é o nível experimental, não a edição nem a disponibilidade. A assinatura pode mudar em uma versão menor, com aviso prévio de descontinuação.
Veja também
Seção intitulada “Veja também”- Contracts: 41 interfaces públicas (SPI) aborda a visão geral da SPI e os níveis de estabilidade.
- Contracts / Document aborda o escritor em memória que estes contratos complementam.
- Writer aborda o emissor de objetos PDF e de referência cruzada.
- HTML / Streaming constraints (ADR-001) aborda a justificativa do escopo de streaming.
- Performance aborda a justificativa de limitação de memória para a saída em streaming.