Generar PDFs HTML en CodeIgniter 4 con Dompdf

Video thumbnail

Los PDFs por sus siglas Portable Document Format, es un formato de archivo ampliamente utilizado para presentar y compartir documentos digitales de forma segura y confiable, tienen la característica que no pueden ser editados de manera directa, como ocurre con un documento de texto,  pueden contener texto e imágenes y se pueden visualizar en la mayoría de los dispositivos y sistemas operativos a través de programas como Adobe o tan fácilmente como los navegadores web.

En CodeIgniter aunque no tenemos muchos paquetes específicos para el framework, disponemos de emplear paquetes regulares para PHP; en este caso de los PDF no es la excepción y en principio podríamos emplear cualquier paquete que sea para PHP para CodeIgniter empleando por supuesto composer para esta labor.

En este tutorial te voy a mostrar cómo integrar Dompdf en CodeIgniter 4 para generar PDFs dinámicos desde vistas HTML, usando datos reales desde base de datos.

¿Qué es Dompdf y por qué usarlo en CodeIgniter 4?

Dompdf es un conversor de HTML a PDF. En esencia, dompdf es (principalmente) un motor de renderizado y diseño HTML compatible con CSS 2.1 escrito en PHP. Es un renderizador basado en estilos: descargará y leerá hojas de estilo externas, etiquetas de estilo en línea y los atributos de estilo de elementos HTML individuales.

El siguiente ejemplo muestra cómo usar Dompdf para convertir HTML y generar PDF con una configuración mínima.

  • Especifique el contenido HTML en el método loadHtml() de la clase Dompdf.
  • Renderiza HTML como PDF usando el método render().
  • Envíe el PDF generado al navegador utilizando el método stream().

Idealmente, el formato PDF se utiliza para leer la información en forma de documento. El archivo PDF se indica con la extensión de archivo .pdf y significa Formato de documento portátil. Un documento PDF manifiesta información para libros electrónicos, formularios de solicitud, manuales de usuario y otros documentos.

En este tutorial, damos por hecho que es un desarrollador en Codeigniter y desea saber cómo crear un archivo PDF a partir de una plantilla de vista HTML usando la biblioteca domPDF.

Muchos desarrolladores novatos se quedan atascados cuando se trata de la conversión de HTML a PDF. Tranquilo, te lo vamos a poner fácil. Al final de este tutorial, puede generar un archivo PDF a partir del diseño HTML con la biblioteca DomPDF.

En esta entrada, vamos a emplear Dompdf por su facilidad y tiempo en el mercado, es una solución ideal para cuando queremos trabajar con los PDFs en PHP; su uso es simple:

Especificamos el HTML que queremos convertir a pdf:

$dompdf->loadHTML('<h1>Hola Mundo</h1><br><p>Otro contenido</p>');

Indicamos el tamaño de la hoja:

$dompdf->setPaper('A4', 'portrait');

Generamos el PDF:

$dompdf->render();

Iniciamos la descarga:

$dompdf->stream();

Instalar Dompdf en CodeIgniter

En CodeIgniter 4, no necesitamos paquetes específicos del framework. Podemos instalar cualquier librería PHP mediante Composer.

composer require dompdf/dompdf

Con esto ya tenemos la librería disponible en nuestro proyecto.

Crear un controlador PDF

Ya con esto, estamos completamente listos para usar Doompdf en nuestra aplicación, empezando, para crear una instancia:

$dompdf = new Dompdf();

Y generamos el pdf y renderizamos:

$dompdf->loadHTML('<h1>Hola Mundo</h1><br><p>Otro contenido</p>');
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$dompdf->stream();

En el caso de CodeIgniter 4, tenemos las funciones de view que nos devuelve el HTML en el controlador; así que las podemos emplear perfectamente en vez de un HTML establecido desde el controlador:

$dompdf = new Dompdf();
$dompdf->loadHTML(view("dashboard/product/trace_pdf", $data));
$productModel = new ProductModel();
$product = $productModel->asObject()->find($productId);

Generar un PDF dinámico desde una vista en CodeIgniter 4

Aquí es donde realmente empieza lo interesante.

En mi curso de CodeIgniter 4 (enlace al final del post), mientras desarrollábamos una app de almacén, necesitábamos generar un reporte completo de trazabilidad de productos. No era solo mostrar datos: había joins, cálculos y totales acumulativos.

$dompdf = new Dompdf();
//$dompdf->loadHTML('<h1>Hola Mundo</h1><br><p>Otro contenido</p>');
$productModel = new ProductModel();
$product = $productModel->asObject()->find($productId);
$query = $productModel->asObject()->select("pc.*, u.email, puc.description, puc.direction")
            ->join('products_control as pc', 'pc.product_id = products.id')
            ->join('users as u', 'pc.user_id = u.id')
            ->join('products_users_control as puc', 'pc.id = puc.product_control_id');
$data = [
            'product' => $product,
            'trace' => $query->where('products.id', $productId)
                ->findAll()
];
$dompdf->loadHTML(
            view("dashboard/product/trace_pdf", $data)
);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$dompdf->stream();

Pasar datos a la vista

$data = [
   'product' => $product,
   'trace' => $query->where('products.id', $productId)->findAll()
];
$dompdf->loadHTML(view("dashboard/product/trace_pdf", $data));

Renderizar el PDF

$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$dompdf->stream();

La vista, luce de la siguiente manera:

<style>
    * {
        color:blue
    }
</style>
<p>
    Ventas y compras de <?= $product->name ?>
</p>
<ul>
    <li>
        Precio <?= $product->price ?>
    </li>
    <li>
        Última Entrada <?= $product->entry ?>
    </li>
    <li>
        Última Salida <?= $product->exit ?>
    </li>
</ul>
<table>
    <thead>
        <tr>
            <th>
                ID
            </th>
            <th>
                Fecha
            </th>
            <th>
                Tipo
            </th>
            <th>
                Cantidad
            </th>
            <th>
                Usuario
            </th>
            <th>
                Descripción
            </th>
            <th>Dirección</th>
            <th>
                Precio
            </th>
            <th>
                Total
            </th>
        </tr>
    </thead>
    <tbody>
        <?php $total = 0 ?>
        <?php foreach ($trace as $key => $t) : ?>
            <tr>
                <td>
                    <?= $t->id ?>
                </td>
                <td>
                    <?= $t->created_at ?>
                </td>
                <td>
                    <?= $t->type ?>
                </td>
                <td>
                    <?= $t->count ?>
                </td>
                <td>
                    <?= $t->email ?>
                </td>
                <td>
                    <?= $t->description ?>
                </td>
                <td>
                    <?= $t->direction ?>
                </td>
                <td>
                    <?= $product->price ?>
                </td>
                <td>
                    <?php $total += $product->price * $t->count ?>
                    <?= $product->price * $t->count ?>
                </td>
            </tr>
        <?php endforeach ?>
        <tr>
            <td colspan="8">Total</td>
            <td><?= $total ?></td>
        </tr>
    </tbody>
</table>

Y esto es todo, aquí lo importante es que podemos extender el framework con cualquier paquete de PHP fácilmente empleando composer, que es la forma recomendada cuando instalamos CodeIgniter 4.

Ejemplo práctico: reporte de inventario en PDF

Este es el punto donde superamos a la mayoría de tutoriales básicos.

La vista puede tener estructura como esta:

<p>Ventas y compras de <?= $product->name ?></p>
<ul>
   <li>Precio <?= $product->price ?></li>
   <li>Última Entrada <?= $product->entry ?></li>
   <li>Última Salida <?= $product->exit ?></li>
</ul>

Cálculo de totales dinámicos

En el proyecto que desarrollé, necesitaba calcular el total dentro del recorrido:

<?php $total = 0 ?>
<?php foreach ($trace as $t) : ?>
   <?php $total += $product->price * $t->count ?>
<?php endforeach ?>

Este detalle marca la diferencia entre un PDF estático y un reporte profesional.

Tabla de movimientos

El uso de tablas es totalmente compatible con Dompdf, siempre que el HTML sea limpio y bien estructurado.

Consejo práctico basado en experiencia:
Evita CSS muy complejo o propiedades modernas avanzadas. Dompdf funciona mejor con estilos simples.

Errores comunes al usar Dompdf en CodeIgniter 4

  • No usar use Dompdf\Dompdf;
  • Problemas de namespace
  • No cargar correctamente la vista
  • Intentar renderizar antes de obtener datos
  • Usar CSS incompatible

Muchos desarrolladores se bloquean aquí, especialmente cuando el PDF sale en blanco. En la mayoría de casos, el problema está en el HTML mal formado.

Buenas prácticas para generar PDFs profesionales

  • Procesa toda la lógica en el controlador.
  • Mantén la vista limpia.
  • Usa estilos simples.
  • Define correctamente tamaño de hoja.
  • Si es un reporte grande, evalúa rendimiento.

En proyectos reales, generar PDFs dinámicos puede impactar memoria si el dataset es muy grande.

Preguntas Frecuentes

  • ¿Cómo generar un PDF desde una vista en CodeIgniter 4?
    • Usando view() para generar el HTML y pasarlo a loadHTML() de Dompdf.
  • ¿Se puede usar cualquier librería PDF en CodeIgniter 4?
    • Sí, gracias a Composer puedes integrar cualquier paquete PHP.
  • ¿Dompdf soporta CSS moderno?
    • Parcialmente. Está basado principalmente en CSS 2.1.

Conclusión

Integrar Dompdf en CodeIgniter 4 es sencillo técnicamente, pero hacerlo bien (con datos reales, consultas complejas y estructura limpia) es lo que marca la diferencia.

En mi experiencia trabajando con reportes de inventario, la clave no está en Dompdf, sino en cómo estructuras tu aplicación MVC antes de renderizar el PDF.

Si entiendes eso, puedes generar:

  • Facturas
  • Reportes
  • Estados de cuenta
  • Trazabilidad
  • Exportaciones dinámicas

Y todo usando vistas HTML normales.

Aprende cómo generar PDF en CodeIgniter 4 con Dompdf paso a paso. Convierte vistas HTML en reportes dinámicos con datos de base de datos.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english
<script> window.addEventListener('scroll', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('mousemove', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('touchstart', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); // Fallback if no interaction window.addEventListener('load', function() { setTimeout(function() { if (!window.scriptsLoaded) loadThirdPartyScripts(); }, 8000); }); function loadThirdPartyScripts() { if (window.scriptsLoaded) return; window.scriptsLoaded = true; console.log('Loading third party scripts...'); // Google Analytics var gtagScript = document.createElement('script'); gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-F22688T9RL'; gtagScript.async = true; document.head.appendChild(gtagScript); gtagScript.onload = function() { window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-F22688T9RL'); }; // Google ADS const adScript = document.createElement('script'); adScript.src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"; adScript.setAttribute('data-ad-client', 'ca-pub-5280469223132298'); adScript.async = true; document.head.appendChild(adScript); // Facebook Pixel (function(f, b, e, v, n, t, s) { if (f.fbq) return; n = f.fbq = function() { n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments) }; if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0'; n.queue = []; t = b.createElement(e); t.async = !0; t.src = v; s = b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t, s); })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1643487712945352'); fbq('track', 'PageView'); } </script> <noscript> <img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1643487712945352&ev=PageView&noscript=1" /> </noscript>