data types and standard library

PDF Generation Overview

PHP does not have one built-in production PDF API. Real projects usually generate PDFs through a Composer library, a command-line renderer, a browser-based renderer, or an external document service.

The junior skill is understanding the workflow around PDF generation: validate the data, render a template, generate the PDF with the chosen tool, store or stream the result, and verify the file that was produced.

Input data -> validated document model -> HTML/template -> PDF renderer -> stored file or response

Start with clean document data

The renderer should not receive raw request data. Build a small document model first.

PHP example
<?php

declare(strict_types=1);

function invoiceSummary(array $invoice): string
{
    if (!isset($invoice['number']) || !is_string($invoice['number']) || trim($invoice['number']) === '') {
        throw new InvalidArgumentException('Invoice number is required.');
    }

    if (!isset($invoice['totalPennies']) || !is_int($invoice['totalPennies']) || $invoice['totalPennies'] < 0) {
        throw new InvalidArgumentException('Invoice total is invalid.');
    }

    return $invoice['number'] . ' / ' . $invoice['totalPennies'] . ' pennies';
}

echo invoiceSummary(['number' => 'INV-2026-001', 'totalPennies' => 129950]) . PHP_EOL;

// Prints:
// INV-2026-001 / 129950 pennies

PDF bugs are often data bugs or template bugs, not only renderer bugs.

Escape template output

If you render HTML before converting to PDF, escape user-controlled text just as you would for a normal web page.

PHP example
<?php

declare(strict_types=1);

$customerName = '<Nia & Co>';

echo htmlspecialchars($customerName, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . PHP_EOL;

// Prints:
// &lt;Nia &amp; Co&gt;

PDF output is not a reason to skip HTML escaping.

Generate safe filenames

PDF files are often stored, emailed, or downloaded. Use controlled filenames.

PHP example
<?php

declare(strict_types=1);

function invoicePdfFilename(string $invoiceNumber): string
{
    if (!preg_match('/^INV-\d{4}-\d{3}$/', $invoiceNumber)) {
        throw new InvalidArgumentException('Invalid invoice number.');
    }

    return strtolower($invoiceNumber) . '.pdf';
}

echo invoicePdfFilename('INV-2026-001') . PHP_EOL;

// Prints:
// inv-2026-001.pdf

Do not use arbitrary customer-provided strings as storage paths.

Choose the rendering approach deliberately

HTML-to-PDF tools are convenient when the PDF looks like a printable web page. Low-level PDF libraries give more control for precise layouts. External services reduce local dependencies but add network, cost, privacy, and failure-mode concerns.

PHP example
<?php

declare(strict_types=1);

$renderer = [
    'type' => 'html-to-pdf',
    'supportsCss' => true,
    'runsInWorker' => true,
];

echo $renderer['type'] . PHP_EOL;

// Prints:
// html-to-pdf

The right choice depends on layout complexity, volume, hosting constraints, and whether generation can run asynchronously.

Verify the result

A generated file should be checked before it is marked complete.

PHP example
<?php

declare(strict_types=1);

function looksLikePdf(string $contents): bool
{
    return str_starts_with($contents, '%PDF-');
}

echo looksLikePdf("%PDF-1.7\n...") ? 'pdf' : 'not pdf';
echo PHP_EOL;

// Prints:
// pdf

In production, also check file size, renderer exit status, logs, and whether the file was saved where the application expects.

What to remember

PDF generation is a workflow, not a single PHP function. Validate the document data, escape templates, choose the renderer intentionally, generate safe filenames, store files outside unsafe paths, and verify the output before sending or recording it.

Practice

Task: Prepare an invoice PDF job

Write a small helper that prepares data for an invoice PDF job.

Requirements

  • Use declare(strict_types=1);.
  • Require an invoice number in the format INV-YYYY-NNN.
  • Require a non-empty customer name.
  • Require a non-negative integer total in pennies.
  • Escape the customer name for HTML template output.
  • Generate a safe lowercase PDF filename.
  • Return a job array containing filename, escaped customer name, and total.
  • Show one valid job and one invalid invoice number.
  • Include the expected output as comments in the same PHP code block.

Do not call a fake PDF library. This task is about preparing the data safely before a real renderer is used.

Show solution
PHP example
<?php

declare(strict_types=1);

function prepareInvoicePdfJob(string $invoiceNumber, string $customerName, int $totalPennies): array
{
    if (!preg_match('/^INV-\d{4}-\d{3}$/', $invoiceNumber)) {
        throw new InvalidArgumentException('Invalid invoice number.');
    }

    $customerName = trim($customerName);

    if ($customerName === '') {
        throw new InvalidArgumentException('Customer name is required.');
    }

    if ($totalPennies < 0) {
        throw new InvalidArgumentException('Invoice total is invalid.');
    }

    return [
        'filename' => strtolower($invoiceNumber) . '.pdf',
        'customerNameHtml' => htmlspecialchars($customerName, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
        'totalPennies' => $totalPennies,
    ];
}

$job = prepareInvoicePdfJob('INV-2026-001', 'Nia & Co', 129950);

echo $job['filename'] . ' / ' . $job['customerNameHtml'] . ' / ' . $job['totalPennies'] . PHP_EOL;

try {
    prepareInvoicePdfJob('../invoice', 'Nia & Co', 129950);
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// inv-2026-001.pdf / Nia &amp; Co / 129950
// Invalid invoice number.

The helper prepares safe input for a real PDF renderer. It validates the document identity, escapes template text, and avoids unsafe filenames before generation starts.