Uma API que se recusa a adivinhar
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
Visão geral
Seção intitulada “Visão geral”O NextPDF faz você dizer exatamente o que quer. Quando a intenção altera os bytes — um nível de assinatura, um destino de saída, um alvo de conformidade —, ela vira um argumento explícito obrigatório, não algo que o motor infere a partir do contexto.
Esta página mostra essa postura no próprio código-fonte do motor: nas assinaturas dos métodos, nos argumentos nomeados e nos pontos em que uma entrada ambígua é rejeitada antes que qualquer byte seja produzido.
Por que isso importa
Seção intitulada “Por que isso importa”Um palpite é uma decisão tomada em seu nome sem aviso. Em um campo de texto, isso é levemente irritante. Em um PDF, é um defeito latente, porque o que você entrega costuma ser um artefato jurídico ou de arquivamento cuja correção será verificada depois por outra pessoa com um validador.
Considere uma assinatura. O digest dela é calculado sobre um intervalo de bytes declarado que exclui deliberadamente o próprio valor da assinatura ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). Uma API que “ajuda” em silêncio — reescrevendo a estrutura, inferindo um nível, preenchendo um espaço reservado — não ajudou. Ela alterou os bytes que uma assinatura deveria proteger. O palpite que parece amigável no ponto de chamada vira um incidente de produção semanas depois. É a mesma linha de código.
A versão curta
Seção intitulada “A versão curta”- Se uma escolha altera a saída e não tem um padrão seguro, o NextPDF a transforma em argumento obrigatório, não em algo inferido.
- Argumentos opcionais com leitura ambígua são nomeados, de modo que o ponto de chamada declare a intenção (
newLine: true, não umtruenu). - Entradas que poderiam ser inseguras são validadas antes da renderização e rejeitadas com uma exceção tipada que nomeia a causa.
- Uma instância de documento é de uso único: é construída, emitida e descartada. Não existe
reset(), então não há “será que isto está sendo reutilizado?” para adivinhar. - O motor nunca emite um artefato de aparência plausível no lugar daquele que você pediu. Em vez disso, ele se recusa.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”O mecanismo é simples, e esse é justamente o ponto. Ele combina sistema de tipos, argumentos nomeados, enums em vez de strings mágicas e um pequeno número de cláusulas de guarda deliberadas colocadas antes da saída.
A tabela compara algumas entradas ambíguas. Para cada uma, mostra o que uma biblioteca que “ajuda” inferiria e o que o NextPDF faz no lugar. Toda coluna do NextPDF descreve um comportamento citado a partir do código-fonte mostrado mais adiante nesta página.
| Entrada ambígua | O que uma biblioteca que adivinha faz | O que o NextPDF faz |
|---|---|---|
Uma string de orientação como "portait" | Usa um padrão e renderiza mesmo assim | addPage() recebe o enum Orientation, não uma string — um erro de digitação é um erro de tipo, não um padrão silencioso |
Um true nu no fim da chamada a cell() | Escolhe a posição booleana que supõe que você quis indicar | O booleano é nomeado no ponto de chamada (newLine: true); um literal sem nome é justamente o cheiro que a API remove |
Um wrapper php:// ou um caminho de travessia passado a save() | ”Faz o melhor que pode” e grava em algum lugar | Rejeitado antes de o PDF ser construído, com uma InvalidConfigException tipada que nomeia a chave, o valor e o tipo esperado |
setSignature() e em seguida save() enquanto o assinador de alto nível não está conectado | Emite um arquivo não assinado que o chamador acredita estar assinado | Lança NotImplementedException antes de produzir bytes, nomeando o caminho suportado |
Reutilizar uma instância de Document para uma segunda renderização | Adivinha se o estado residual ainda se aplica | Sem reset() e sem caminho de reutilização — uma instância nova por requisição via DocumentFactory, então não há estado residual a adivinhar |
A intenção é um argumento obrigatório. O contrato central, PdfDocumentInterface, recebe geometria e alinhamento como objetos de valor tipados e enums, não primitivos soltos:
public function addPage( ?PageSize $size = null, Orientation $orientation = Orientation::Portrait,): static;
public function cell( float $width, float $height, string $text = '', bool|string $border = false, bool $newLine = false, Alignment $align = Alignment::Left, bool $fill = false,): static;Orientation e Alignment são enums, então a chamada não pode passar "portait" e fazer isso significar “padrão” em silêncio. Quando existe um padrão, ele é seguro (retrato, à esquerda, sem borda), não um palpite sobre o que você provavelmente queria.
Booleanos ambíguos são nomeados no ponto de chamada. Ao longo dos exemplos que servem como a referência de fato da API, a mesma forma se repete:
$document->cell(0, 15, 'Hello, NextPDF!', newLine: true);$document->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);$pdf = $document->output(dest: OutputDestination::String);newLine: true é inequívoco. Um true nu no fim não seria. O nível da assinatura é SignatureLevel::PAdES_B_B, um caso de enum — nunca uma string que o motor precise interpretar. O destino de saída é OutputDestination::String, então “me dê os bytes, sem cabeçalhos HTTP, sem arquivo” está declarado. Não é inferido com base em o nome de um arquivo ter sido passado ou não.
Entradas inseguras são rejeitadas antes de um byte ser gravado. save() valida o caminho de destino antes de construir o PDF:
public function save(string $path): void{ // Reject stream wrappers and null bytes if (\str_contains($path, "\0") || \preg_match('#^[a-zA-Z]+://#', $path)) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $path, expectedType: 'valid_path', ); } // Resolve the parent directory to prevent path traversal $dir = \dirname($path); $realDir = \realpath($dir); if ($realDir === false) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $dir, expectedType: 'existing_directory', ); } // ... only now is the PDF built and written atomically}O motor não “faz o melhor que pode” com um wrapper php:// ou um caminho de travessia. Ele se recusa, e a exceção nomeia a chave, o valor e o esperado.
O motor se recusa em vez de emitir um artefato enganoso. A forma mais forte de se recusar a adivinhar é não produzir saída alguma quando essa saída seria falsa. Quando uma assinatura de alto nível está configurada, mas a costura do escritor que de fato assinaria não está conectada, o caminho de construção lança antes de produzir bytes, em vez de emitir um arquivo não assinado que o chamador acreditaria estar assinado:
if ($this->padesOrchestrator !== null) { throw new NotImplementedException( feature: 'Document::setSignature()->save()/output()/getPdfData()', followUp: 'The high-level PAdES writer seam is not yet wired ... ' . 'Produce a signed PDF via the direct two-phase ' . 'PadesOrchestrator::signDocument() then finalizeSignature() ' . 'buffer API ...', );}Um PDF não assinado que parece assinado é exatamente o tipo de artefato errado, mas plausível, que este princípio existe para evitar. A mesma postura aparece no caminho estrito de CSS. Um desvio de especificação não registrado lança uma StrictModeViolation no ponto de detecção, em vez de renderizar uma aproximação e deixar o desvio passar despercebido.
O uso único elimina toda uma classe de palpites. Um Document é descartável — construído, emitido e descartado. Não existe reset() nem caminho de reutilização. Um worker de longa duração cria uma instância nova por requisição através do DocumentFactory. O motor nunca precisa adivinhar se o estado residual de um documento anterior ainda é significativo, porque, por construção, esse estado não existe.
O que as evidências dizem
Seção intitulada “O que as evidências dizem”Esta página é Evidence: Code-backed : toda forma acima é citada a partir do próprio código-fonte do motor e de seus exemplos, não parafraseada com base na intenção.
- As assinaturas tipadas, portadoras de enums, são o contrato público em
PdfDocumentInterface. O estilo de chamada com argumentos nomeados é a forma usada de modo consistente em todos os exemplos canônicos que atuam como a referência de fato da API. - A validação do caminho antes da renderização, com sua
InvalidConfigExceptiontipada, e a guarda de recusa antes da emissão,NotImplementedException, são citadas literalmente a partir do caminho de saída da fachada do documento. - A âncora de padrões é Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — proteção contra erro do usuário, a propriedade de qualidade que uma API que se recusa a adivinhar existe para satisfazer no ponto de chamada. A segunda âncora é Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 , e é por isso que adivinhar em torno de um documento assinado nunca é inofensivo. O digest cobre um intervalo de bytes declarado que exclui o valor da assinatura, então qualquer reescrita silenciosa o invalida.
Exemplo prático
Seção intitulada “Exemplo prático”Veja um programa pequeno e completo. Toda linha que poderia ser ambígua declara sua intenção. A única entrada insegura é recusada antes que qualquer trabalho seja feito.
<?php
declare(strict_types=1);
use NextPDF\Contracts\OutputDestination;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use NextPDF\ValueObjects\PageSize;use NextPDF\Contracts\Orientation;
$document = Document::createStandalone();$document->setTitle('Quarterly Report');
// Intent is explicit: a typed page size and an Orientation enum case,// not a string the engine has to interpret.$document->addPage(PageSize::a4(), Orientation::Landscape);$document->setFont('helvetica', 'B', 16);
// Ambiguous boolean is named, so the call reads as intent.$document->cell(0, 12, 'Quarterly Report', newLine: true);
try { // Unsafe path is rejected before a byte is built. $document->save('php://output/report.pdf');} catch (InvalidConfigException $e) { // "Invalid configuration for key "output_path": expected valid_path, ..." error_log($e->getMessage());
// The String destination is explicit: bytes only, no HTTP headers, // no file side effect. Nothing is inferred from a missing filename. $bytes = $document->output(dest: OutputDestination::String);}Não há caminho em que este programa faça a coisa errada em silêncio. Ele declara a intenção e prossegue, ou nomeia o problema e para.
Equívoco comum
Seção intitulada “Equívoco comum”A objeção frequente é “isso é só verbosidade”. Não é verbosidade. É a ausência de padrões ocultos. Um true nu é mais curto que newLine: true exatamente na medida da clareza que ele remove. O motor troca alguns caracteres no ponto de chamada pela eliminação de uma categoria de bug — aquela em que o código compila, executa, produz um arquivo e está errado.
Um equívoco relacionado é achar que falhar rápido significa “lança muito”. No uso normal, o NextPDF não lança nada. Entradas válidas fluem normalmente. As guardas disparam apenas em entradas que são genuinamente ambíguas ou inseguras — precisamente as entradas sobre as quais você quer saber de imediato, não aquelas que você quer que sejam adivinhadas.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”Recusar-se a adivinhar aplica-se a intenção e segurança, não a toda conveniência. O NextPDF ainda tem padrões seguros: orientação retrato, alinhamento à esquerda, sem borda. O princípio é oferecer um padrão somente onde ele é seguro e não traz surpresas, nunca onde a inferência errada produz um documento errado.
Esta página demonstra o princípio na superfície central da API pública (a fachada do documento, seu contrato e o caminho de saída). Os subsistemas têm seus próprios pontos de entrada, e cada um documenta o próprio comportamento de validação. As formas citadas aqui estão atualizadas no momento desta revisão. Elas ilustram o padrão; não são um catálogo exaustivo de todas as guardas do motor.
As guardas de falha rápida descritas são guardas de correção e segurança. Por si só, não constituem uma fronteira de segurança. A validação de entrada é uma camada. A filosofia de design e a documentação de segurança descrevem a postura mais ampla.
Documentos relacionados
Seção intitulada “Documentos relacionados”- A filosofia de design do NextPDF — o princípio que esta página demonstra, em seu contexto de prioridades.
- Erros como recurso — o que as exceções tipadas que essas guardas lançam foram projetadas para dizer a você.
- Tipos estritos, em toda parte — como o sistema de tipos torna “declare sua intenção” algo aplicável em vez de apenas recomendado.
Glossário
Seção intitulada “Glossário”- Com respaldo no código (nível de evidência) — uma página cujas afirmações são verificadas contra o próprio código-fonte do motor ou contra um exemplo executável, citado em vez de parafraseado.
- Falhar rápido — rejeitar uma entrada inválida no ponto mais inicial possível, com uma causa clara, em vez de prosseguir e falhar de forma obscura mais tarde.
- Argumento nomeado — uma sintaxe de ponto de chamada do PHP (
newLine: true) que vincula um valor a um parâmetro pelo nome, tornando autoexplicativo um literal de outra forma ambíguo. - Ciclo de vida de uso único — o contrato descartável de
Document: instanciar, escrever, salvar, descartar. Semreset(), sem reutilização. Os workers criam uma instância nova por requisição através doDocumentFactory. - PAdES — PDF Advanced Electronic Signatures, a família de perfis ETSI para assinatura de PDF. Expandido no primeiro uso; coberto em profundidade nas páginas de assinatura.