Guía para desarrolladores de Symfony
De un vistazo
Sección titulada «De un vistazo»El paquete de Symfony está orientado a servicios. Inyectar PdfFactory, llamar a create() para cada documento y usar los builders de Messenger para la generación asíncrona. La factory puede ser un servicio del contenedor porque cada llamada devuelve un documento nuevo.
Usar esta guía al diseñar controladores, servicios, handlers de Messenger o puntos de extensión a nivel de bundle en torno a nextpdf/symfony.
Límites de la arquitectura
Sección titulada «Límites de la arquitectura»| Capa | Propiedad de | Responsabilidad | No incluir aquí |
|---|---|---|---|
| Controlador | Aplicación | Autorizar la solicitud, recopilar la entrada y devolver PdfResponse. | Maquetación de PDF compartida entre casos de uso. |
| Servicio de aplicación | Aplicación | Cargar los datos de dominio y elegir un builder. | Lógica del compilador del contenedor de Symfony. |
| Servicio builder | Aplicación | Implementar PdfBuilderInterface para construir documentos de forma síncrona o en cola. | Objetos de solicitud, entity managers o contexto no serializable. |
| Bundle de Symfony | nextpdf/symfony | Registrar servicios, el árbol de configuración, el paso de extensión opcional, los helpers de respuesta y los DTO de Messenger. | Política de almacenamiento específica de cada tenant. |
| Motor principal | nextpdf/nextpdf | Crear y serializar el documento. | Comportamiento de la respuesta de Symfony o de Messenger. |
Ciclo de vida en tiempo de ejecución
Sección titulada «Ciclo de vida en tiempo de ejecución»| Etapa | Comportamiento | Acción del desarrollador |
|---|---|---|
| Arranque del bundle | NextPdfBundle::build() registra la detección de extensiones opcionales. | Dejar que Symfony descubra el bundle o registrarlo en bundles.php. |
| Carga de la configuración | NextPdfExtension::load() procesa la configuración de nextpdf: y carga las definiciones de servicio. | Mantener una configuración explícita y sensible al entorno. |
| Uso de la factory | PdfFactory::create() devuelve un documento nuevo ya configurado. | No almacenar documentos en los servicios. |
| Salida del controlador | PdfResponse convierte un documento terminado en una respuesta. | Usar el helper en lugar de ensamblar las cabeceras manualmente. |
| Despacho de Messenger | GeneratePdfMessage transporta la clase del builder, la ruta de salida y el contexto serializable. | Mantener un contexto mínimo y apto para valores escalares. |
| Manejo del mensaje | GeneratePdfHandler resuelve el builder desde un service locator y guarda el documento. | Hacer que los builders sean deterministas e idempotentes. |
Estructura recomendada de la aplicación
Sección titulada «Estructura recomendada de la aplicación»| Ruta | Propósito |
|---|---|
src/Pdf/Builder/* | Servicios que implementan PdfBuilderInterface. |
src/Pdf/Data/* | DTO pequeños o arrays utilizados como contexto del builder. |
src/Pdf/Storage/* | Selección del directorio raíz de almacenamiento y política de nombres de archivo de salida. |
src/Controller/* | Puntos de entrada de respuesta síncronos. |
tests/Pdf/* | Pruebas de builders, respuestas, Messenger y configuración. |
Preferir los servicios builder en lugar de funciones helper estáticas. Son fáciles de etiquetar, decorar, probar y usar desde Messenger.
<?php
namespace App\Pdf\Builder;
use NextPDF\Core\Document;use NextPDF\Symfony\Message\PdfBuilderInterface;
final readonly class InvoicePdfBuilder implements PdfBuilderInterface{ public function build(Document $document, array $context): Document { $document->setTitle((string) $context['title']) ->addPage() ->writeHtml((string) $context['html']);
return $document; }}Patrón de respuesta síncrona
Sección titulada «Patrón de respuesta síncrona»<?php
namespace App\Controller;
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;
final readonly class InvoiceController{ public function __invoke( PdfFactory $factory, InvoicePdfBuilder $builder, ) { $document = $builder->build($factory->create(), [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ]);
return PdfResponse::download($document, 'invoice-1234.pdf'); }}Mantener reducido el contexto del controlador. Si un builder necesita muchos objetos de dominio, mover la orquestación a un servicio de aplicación y pasar al builder un DTO o un array normalizado.
Patrón de Messenger
Sección titulada «Patrón de Messenger»GeneratePdfMessage valida la clase del builder y la ruta de salida antes del despacho. El handler vuelve a validar la ruta en tiempo de ejecución.
<?php
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Message\GeneratePdfMessage;
$bus->dispatch(new GeneratePdfMessage( builderClass: InvoicePdfBuilder::class, outputPath: $projectDir . '/var/pdfs/invoice-1234.pdf', builderContext: [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ],));No colocar en builderContext entidades de Doctrine, streams abiertos, closures, objetos de solicitud ni objetos de servicio.
Puntos de extensión
Sección titulada «Puntos de extensión»| Punto de extensión | Uso recomendado | Restricción |
|---|---|---|
Decoración de servicio de PdfFactory | Aplicar valores predeterminados de la aplicación antes de que los documentos lleguen a los controladores. | Debe preservar la semántica de documento nuevo. |
PdfBuilderInterface | Definir builders de documentos reutilizables o en cola. | Debe devolver un Document. |
OptionalExtensionPass | Habilitar funcionalidades opcionales de Artisan o Premium en tiempo de compilación. | La disponibilidad es el estado de compilación del contenedor, no el estado de la solicitud. |
| Árbol de configuración de Symfony | Valores predeterminados, PDF/A, ajustes del renderer, firma, TSA, Messenger. | Una configuración no válida debería fallar durante la compilación del contenedor. |
Cableado de servicio de GeneratePdfHandler | Restringir qué builders son accesibles desde los mensajes en cola. | El service locator solo debería exponer los servicios builder aprobados. |
Flujo de trabajo de desarrollo
Sección titulada «Flujo de trabajo de desarrollo»- Añadir un servicio builder con entrada determinista.
- Usar
PdfFactory::create()en un controlador o servicio. - Añadir una prueba de respuesta para el nombre de archivo, el tipo de contenido y las cabeceras.
- Registrar el builder para Messenger cuando el mismo documento deba generarse de forma asíncrona.
- Añadir pruebas de mensaje no válido para el nombre de clase, la ruta de salida y la forma del contexto.
- Añadir una prueba de compilación del contenedor con configuración mínima y de producción.
- Medir el tiempo de renderizado y la memoria con los mismos ajustes de PHP que en producción.
Manejo de fallos
Sección titulada «Manejo de fallos»| Fallo | Dónde debería manejarse | Respuesta recomendada |
|---|---|---|
| Configuración no válida | Compilación del contenedor. | Hacer que el despliegue falle antes de que el tráfico llegue a la aplicación. |
| Servicio builder ausente | Pruebas del handler de Messenger y tags de servicio. | Hacer que el mensaje falle y alertar al equipo responsable. |
| Ruta de salida insegura | Constructor del mensaje y política de almacenamiento. | Rechazarla antes del despacho; mantener la validación del handler como defensa en profundidad. |
| Extensión opcional no disponible | Compiler pass y comportamiento de la factory. | Desactivar la funcionalidad opcional o hacer explícita la instalación. |
| Fallo de conversión del servicio o de renderizado | Límite del builder. | Fallar de forma cerrada salvo que el caso de uso tenga un fallback documentado. |
Valores predeterminados seguros
Sección titulada «Valores predeterminados seguros»| Aspecto | Predeterminado | Cuándo anularlo |
|---|---|---|
| Tiempo de vida de la factory | Servicio del contenedor. | Mantenerlo; la factory es segura porque crea documentos. |
| Tiempo de vida del documento | Una unidad de trabajo. | Nunca compartirlo entre solicitudes o mensajes. |
| Validación de la ruta de salida | Constructor del mensaje y handler. | Añadir restricciones de tenant o de directorio raíz de almacenamiento en el código de la aplicación. |
| Nombre de archivo de la respuesta | document.pdf. | Anularlo con identificadores de negocio saneados. |
| Transporte de Messenger | async. | Usar un transporte dedicado cuando el trabajo de PDF sea pesado. |
Lista de comprobación de pruebas
Sección titulada «Lista de comprobación de pruebas»- Las pruebas del contenedor compilan el bundle con configuración mínima y de producción.
- Las pruebas de respuesta verifican las cabeceras de seguridad y el manejo del nombre de archivo.
- Las pruebas de Messenger verifican que las rutas no válidas y los nombres de clase de builder no válidos fallen antes del despacho.
- Las pruebas del handler usan un servicio builder real y un directorio de salida temporal.
- Las pruebas del builder renderizan un documento representativo y lo guardan con permisos del sistema de archivos similares a los de producción.
- Las pruebas de extensión opcional cubren los casos de Artisan no disponible, Premium no disponible y el comportamiento del perfil PDF/A configurado.