Ir al contenido

Core / trait HasOutput

HasOutput es el trait del concern encargado de serializar el documento. Proporciona output(), save() y getPdfData(), junto con accesores a los motores internos.

Ventana de terminal
composer require nextpdf/core:^3

trait HasOutput (src/Core/Concerns/HasOutput.php, desde 1.2.0) se integra en Document. Se encarga del paso final: convertir el modelo en memoria en bytes de PDF y entregarlos.

output(?string $filename = null, OutputDestination $dest = OutputDestination::Inline): string construye el PDF y lo entrega. OutputDestination es un enum respaldado por cadenas con cuatro casos: Inline ('I'), Download ('D'), File ('F') y String ('S'). Inline establece Content-Type: application/pdf con una disposición en línea. Download envía una disposición de adjunto. File escribe en $filename y lanza InvalidConfigException cuando $filename es null. String devuelve los bytes sin enviar encabezados. En todos los casos, el método devuelve el binario PDF en bruto.

save(string $path): void construye el PDF y lo escribe en un archivo. Rechaza los envoltorios de flujo y los bytes nulos en la ruta. Resuelve el directorio padre con realpath() para impedir el recorrido de rutas. Luego escribe mediante AtomicFileWriter. Un lector concurrente verá el archivo anterior o los bytes nuevos, nunca una escritura parcial. getPdfData(): string construye y devuelve los bytes, y deja la entrega a cargo de quien lo invoca.

El paso de construcción es interno. buildPdf() bloquea las banderas experimentales, vuelca la página activa, reemplaza el alias del recuento total de páginas en cada flujo de contenido de página y serializa mediante PdfWriter. Cada página se convierte en uno o más flujos de contenido (ISO 32000-2:2020 §7.8.2). El árbol de páginas emitido por el writer mantiene Count coherente con Kids (§7.7.3).

El trait también expone accesores a motores: getFontRegistry(), getWriter(), getDrawingEngine(), getTransformEngine(), getTextRenderer(), getHeaderFooter(), getBookmarkManager() y getLinkManager(). Los accesores diferidos a motores —getFormFieldManager(), getLayerManager(), getTemplateManager(), getFileAttachment(), getJavaScriptManager()— devuelven null hasta que la función se usa por primera vez.

SímboloTipoEstabilidadDesde
output(?string, OutputDestination): stringmétodoestable1.2.0
save(string $path): voidmétodoestable1.2.0
getPdfData(): stringmétodoestable1.2.0
getFontRegistry(): FontRegistryInterfacemétodoestable1.2.0
getWriter(): PdfWritermétodoestable1.2.0
getDrawingEngine(): DrawingEnginemétodoestable1.2.0
getFormFieldManager(): ?FormFieldManagermétodoestable1.2.0
getLayerManager(): ?LayerManagermétodoestable1.2.0
enableV2ContentStream(bool): staticmétodoobsoleto (1.12.0)1.2.0
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');
echo "Created: output/01-hello-world.pdf\n";

Fuente: derivado de examples/01-hello-world.php.

getPdfData() devuelve los bytes para que un controlador los transmita, de modo que el framework controle la respuesta HTTP.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
function buildInvoicePdf(string $invoiceNo): string
{
$doc = Document::createStandalone();
$doc->setTitle("Invoice {$invoiceNo}");
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, "Invoice {$invoiceNo}", newLine: true);
return $doc->getPdfData();
}
// In a PSR-7 controller:
// return $response
// ->withHeader('Content-Type', 'application/pdf')
// ->withBody($streamFactory->createStream(buildInvoicePdf('001')));

Fuente: patrón tomado de examples/02-pdf-factory.php.

  • output() con OutputDestination::File lanza InvalidConfigException cuando $filename es null.
  • output() omite la emisión de encabezados cuando headers_sent() devuelve true, así que se mantiene seguro tras una salida parcial.
  • save() rechaza cualquier ruta con un prefijo de envoltorio de flujo (scheme://) o un byte nulo.
  • save() requiere que el directorio padre exista. Un directorio que no se puede resolver lanza InvalidConfigException.
  • enableV2ContentStream() está obsoleto desde 1.12.0. La bandera se ignora; se debe eliminar la llamada.
  • Los accesores de motores diferidos devuelven null antes del primer uso de la función. Debe comprobarse si es null antes de desreferenciar.

buildPdf() serializa una única vez en O(total de bytes de contenido). No realiza una segunda pasada sobre un árbol retenido (ADR-001). save() añade un único renombrado atómico. La salida es determinista byte a byte con DeterministicSettings. DeterministicSettings admite el perfil de reproducibilidad a nivel de bits. Presupuesto: 1500 ms / 64 MB para el inicio rápido canónico.

save() bloquea el recorrido de rutas y los envoltorios de flujo, y luego escribe de forma atómica mediante AtomicFileWriter. Esto cierra la ventana TOCTOU del archivo producido. output() sanea el nombre de archivo en el encabezado Content-Disposition. Las advertencias se escriben en un archivo lateral junto al PDF y se envían a stderr; nunca se insertan en línea dentro de los bytes del PDF.

AfirmaciónFuenteCláusulareference_id
Cada página se serializa como uno o más flujos de contenidoISO 32000-2:2020§7.8.2
Count se escribe de forma coherente con Kids en el arregloISO 32000-2:2020§7.7.3

Las cláusulas están parafraseadas. No se reproduce ningún texto normativo.

  • /modules/core/core/document-facade/ — la fachada que compone HasOutput
  • /modules/core/core/ — panorama de Core y el modelo de traits de concern
  • /modules/core/writer/ — writer mediante el cual serializa PdfWriterbuildPdf()
  • /modules/core/core/has-security/HasSecurity: cifrado aplicado antes de la serialización
  • /modules/core/core/has-pages/HasPages: flushCurrentPage() invocado por buildPdf()
  • /modules/core/contracts/ — enum OutputDestination usado por output()
  • /modules/core/exception/InvalidConfigException cuando File recibe un nombre de archivo null o una ruta incorrecta <!— evidence: src/Core/Concerns/HasOutput.php (trait HasOutput @since 1.2.0; output(?string=null,OutputDestination=Inline):string — File throws InvalidConfigException when filename null, Inline/Download set Content-Type+Content-Disposition guarded by !headers_sent(), sanitizeHeaderFilename; save(string):void — rejects \0 + ^[a-z]+:// stream wrappers, realpath parent dir, AtomicFileWriter::write, writeWarningSidecar; getPdfData():string; buildPdf() lockExperimentalFlags+flushCurrentPage+NB_PAGES_ALIAS replace; getFontRegistry/getWriter/getDrawingEngine/getTransformEngine/getTextRenderer/getHeaderFooter/getBookmarkManager/getLinkManager; getFormFieldManager/getLayerManager/getTemplateManager/getFileAttachment/getJavaScriptManager nullable; enableV2ContentStream @deprecated 1.12.0 ignored); src/Contracts/OutputDestination.php (enum:string Inline=I/Download=D/File=F/String=S); src/Core/DeterministicSettings.php; ADR-001; examples/01-hello-world.php, examples/02-pdf-factory.php. RAG iso32000_2_sec7 §7.8.2 , §7.7.3 >