Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Pdf Laravel Package

littlerocket/php-pdf

A lightweight PHP package from littlerocket for generating PDFs with a clean, expressive API. Build documents programmatically, add text, images and pages, then stream or save the output—designed to fit neatly into Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

littlerocket/php-pdf is a fork of psliwa/PHPPdf — an XML-driven layout engine that turns a small markup document into a PDF (or graphic) file via Zend Framework 1's PDF stack. You describe the document declaratively, the library lays it out (flowing text, columns, tables, page breaks), and you get back a Zend PDF object you can save or stream.

1. Install and verify it actually resolves

composer require littlerocket/php-pdf

If the fork isn't published under that name on Packagist (likely, given 0 stars / 0 dependents), add it as a VCS repo:

{
  "repositories": [{ "type": "vcs", "url": "https://github.com/littlerocketdev/PHPPdf" }],
  "require": { "littlerocket/php-pdf": "dev-master" }
}

Look at composer.json first. This library historically depends on Zend Framework 1 components (Zend_Pdf, Zend_Barcode, Zend_Cache), which are abandoned and were never PHP 8-clean (each(), create_function, curly-brace string offsets, dynamic properties). The "fixed for PHP8" claim means the fork either patched those or vendors replaced copies — confirm which, because it determines whether you can composer update safely later. Run the test suite (if present) and a smoke script on your target PHP minor before writing feature code.

2. First render

use PHPPdf\Core\FacadeBuilder;

$facade = FacadeBuilder::create()->build();

$pdf = $facade->buildPdfFromString(<<<XML
<document>
    <page format="A4">
        <paragraph font-size="18" text-align="center">
            <text>Invoice #1234</text>
        </paragraph>
        <paragraph>
            <text>Thanks for your business.</text>
        </paragraph>
    </page>
</document>
XML);

$pdf->save(storage_path('app/invoices/1234.pdf'));

buildPdfFromXml($path) is the file-based equivalent. Both return the underlying Zend PDF document, so save() and render() are yours to call.

3. Where to look next

  • The Facade / FacadeBuilder classes — the entire public API surface is essentially those two.
  • The examples/ directory and the original psliwa/PHPPdf docs/wiki. The fork rarely rewrites documentation, so upstream docs are your real reference for the XML dialect (elements, attributes, stylesheets).
  • The XML node/element classes if you need to know exactly which attributes are honoured — the markup is a custom CSS-like dialect, not HTML, and guessing attributes will silently do nothing.

Implementation Patterns

Treat the Facade as a long-lived service, not a per-call object. Building it wires up fonts, cache, and the engine; construct once per request/worker and inject it.

// app/Services/PdfRenderer.php
final class PdfRenderer
{
    private Facade $facade;

    public function __construct()
    {
        $this->facade = FacadeBuilder::create()
            ->setCacheDir(storage_path('framework/pdf-cache'))
            ->build();
    }

    public function render(string $xml): \ZendPdf\PdfDocument
    {
        return $this->facade->buildPdfFromString($xml);
    }
}

Bind it as a singleton in a service provider so queue workers reuse the parsed-font/cache state across jobs.

Templates on disk + data binding. Keep the markup in resources/pdf/*.xml and bind data before parsing. Since this is XML (not Blade-safe by default), escape values explicitly:

$xml = str_replace(
    ['{{customer}}', '{{total}}'],
    [htmlspecialchars($customer, ENT_XML1), number_format($total, 2)],
    file_get_contents(resource_path('pdf/invoice.xml'))
);

For anything with loops (line items) or optional blocks, use DOMDocument to clone a <row> template node per item rather than string concatenation — you avoid malformed-XML failures that the parser reports with confusing line numbers.

Use stylesheets instead of repeating attributes. Define named styles once and apply with class; this is the difference between a maintainable template and 400 lines of duplicated font-size attributes. Put shared styles (margins, base font, heading sizes) in one included block and keep per-template overrides minimal.

Generate off the request thread. Layout is CPU- and memory-heavy (font metrics, line breaking, table measurement). For anything beyond a one-page document, dispatch a job:

class GenerateInvoicePdf implements ShouldQueue
{
    public function handle(PdfRenderer $renderer): void
    {
        $pdf = $renderer->render($this->xml);
        $pdf->save($tmp = tempnam(sys_get_temp_dir(), 'pdf'));
        Storage::disk('s3')->put("invoices/{$this->id}.pdf", fopen($tmp, 'r'));
    }
}

Stream to the browser when synchronous is fine. Never echo before rendering; set headers and write the bytes:

return response()->streamDownload(function () use ($pdf) {
    echo $pdf->render();
}, 'invoice.pdf', ['Content-Type' => 'application/pdf']);

Two output paths, one template. Because the same markup can go to PDF or to the graphic renderer, keep templates renderer-agnostic — avoid PDF-only constructs (barcodes, embedded fonts) in templates you also want as PNG/JPEG previews.

Snapshot-test the output. Generate a fixture PDF in CI and assert on extracted text (pdftotext) rather than byte hashes — timestamps and object ordering make binary diffs useless.

Gotchas and Tips

The dependency chain is the real risk, not the markup. ZF1 is abandoned; PHP 8.2+ emits deprecation notices for dynamic properties throughout it, and 8.4 tightens implicit-nullable parameters. Suppress deprecations narrowly (don't set error_reporting(0) globally), and pin the exact fork commit in composer.lock. With 0 stars and 0 dependents, there is no upstream to fix a break — assume you may need to vendor the source into your repo and patch it yourself. Budget for that.

Unicode is the classic failure. Zend PDF's built-in fonts are WinAnsi-only. Any non-Latin-1 character (Polish diacritics, CJK, emoji, curly quotes) renders as ? or garbage unless you register a TTF font with proper encoding and reference it by name in the markup. Register fonts once at Facade construction and warm the cache — first-time TTF metric parsing is slow.

Fonts and cache directories must be writable and absolute. Relative paths resolve against the CWD, which differs between php artisan serve, queue workers, and Octane. Use storage_path() and make sure the process user can write there; a read-only cache directory typically manifests as a silent performance collapse or a vague exception, not a clear error.

Stale cache is the #1 "why didn't my template change apply?" bug. The parsed-document cache is keyed by content/config; if you're loading XML from a file the key may not include mtime. Clear the cache directory in your deploy script, and disable caching (setDebug(true) on the builder, or the equivalent no-cache path) while iterating on layout.

Escape everything that enters <text>. A single & or < in a customer name aborts the whole render. htmlspecialchars($v, ENT_XML1 | ENT_QUOTES, 'UTF-8') on every interpolation point; prefer DOM building for user-controlled content.

Errors are terse and sometimes misleading. Parse failures usually include the XML line/column — trust those first, they're accurate. Layout failures (overflow, zero-size pages, unsupported attribute combos) often surface as generic exceptions or, worse, a blank page with no error. Bisect by rendering a minimal document and adding nodes back one at a time.

Images are constrained. Expect JPEG/PNG only, and scaling/conversion may route through GD or Imagick. Verify the extension is installed in the same PHP SAPI that runs your workers.

Unsupported attributes fail silently. The dialect is CSS-like, not CSS. If a value doesn't apply, the renderer generally ignores it rather than erroring. When something "doesn't work," check the element class source for the attribute name before assuming a bug — typos and HTML-isms (align instead of text-align, etc.) are the usual culprits.

Memory and time limits bite on multi-page documents. A 50-page table can easily exceed 256 MB. Set memory_limit/max_execution_time on the job, and chunk genuinely huge documents into multiple files rather than fighting the engine.

Plan an exit. If this is greenfield and you don't specifically need PHPPdf's XML flow-layout model, dompdf/mpdf/TCPDF or a headless-Chrome renderer will cost you less over a project's lifetime. Adopt this fork when you're maintaining an existing PHPPdf-based system or need its specific declarative layout, and wrap it behind your own PdfRenderer interface so swapping the engine later is a one-class change.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons