pontedilana/php-weasyprint
PhpWeasyPrint is a PHP wrapper for WeasyPrint that generates PDFs from URLs or HTML. Inspired by Snappy and built as a drop-in replacement, it offers a strictly typed GeneratorInterface and requires PHP 8.3+ plus WeasyPrint 60 or greater.
Architecture fit. PhpWeasyPrint is a thin, framework-agnostic CLI wrapper: it composes a weasyprint argument array, hands it to symfony/process, and returns bytes or writes a file. It has no Laravel coupling, no service provider, no facade, no config — which is a fit for our architecture rather than a gap, because it slots under a job/service layer we already own. The surface a TPM needs to reason about is small: generateFromHtml, generateFromUrl, getOutput, setOption, setTimeout. The GeneratorInterface mirrors KnpLabs/snappy's contract (string-only input, typed returns), so if we have any Snappy-based code, the call sites are nearly identical and our abstraction boundary can stay the same.
Integration feasibility. Composer install is trivial; symfony/process is already transitively present in Laravel 10/11 and is pinned here to a CVE-patched floor. The real integration cost is not PHP — it is the runtime: WeasyPrint ≥ 60 requires Python 3.9+, Pango, cairo, gdk-pixbuf, harfbuzz, and a populated font set in every environment (local dev, CI, staging, prod, and any future container base image change). PHP 8.3–8.5 support means Laravel 10.48+ or 11+, so this package also silently sets a floor on our framework upgrade cadence.
Technical risk.
--pdf-tags, --xmp-metadata, --allowed-protocols, --srgb), but nothing validates that the installed binary supports a given flag. A container image bump can break a template at runtime, not at build time. Mitigation: pin WeasyPrint version explicitly in the image and assert it at boot (--version).composer update can be security-relevant. Output-path and URL-scheme allow-lists are now defaults; we should not widen them.Key questions.
generateFromUrl at all, or is HTML-string-only sufficient? URL fetching is the SSRF risk and should be disabled by default.--pdf-variant/--pdf-tags become hard requirements and lock the version floor.Stack fit. Treat the package as a leaf dependency behind our own PdfRenderer interface, bound in a small service provider we write ourselves. Configuration belongs in config/pdf.php (binary path, timeout, allowed schemes, default options), resolved via env() so containers can override without a code change. Controllers should never touch Pdf directly; they dispatch a job or call the service, and the service returns either a streamed response or a Storage path. Output files go to a Laravel disk, not /tmp, so retention and cleanup are ours.
Migration path. Two lanes. From Snappy: low effort — the method contract matches, so swap the interface implementation, delete the old binary, and run our golden-template regression suite. From dompdf or greenfield: medium effort, dominated by template rework, since WeasyPrint's strength (paged media, @page rules, running headers, page counters) rewards CSS we may not have written yet.
Compatibility. Publish an explicit matrix and enforce it: Laravel version → PHP version → WeasyPrint version → package major. WeasyPrint version must be pinned in the image and asserted by a health check; the package does not do this for us.
Sequencing.
PdfRenderer interface, one queue job, storage wiring, timeout policy aligned with queue worker timeout (disableTimeout() inside workers so timeouts live in one place).Total: roughly 4–5 engineering weeks including image work — of which about 60% is not about this package at all.
Maintenance. Two coupled upgrade streams: package majors (frequent — 2.0 through 2.6 across recent months) and WeasyPrint majors (roughly quarterly). Each pairing needs a smoke render of our golden templates in CI. Budget ~2 days per quarter for version bumps plus reactive security patches. The recurring CVE pattern means we should enable automated dependency alerts and treat symfony/process advisories as patch-now, not patch-later.
Support. Community support is thin: no Slack, no commercial backing, issue templates that demand a full OS/binary/HTML reproducer. Our first-line support must therefore own a runbook with three checks — binary present and --version matches, template renders in the pinned image locally, exit code and stderr captured from the exception — before anything is escalated upstream. Assume we are the support tier; there is no vendor.
Scaling. PDF generation is CPU- and memory-bound on the Python process, not on PHP. Horizontal scaling means more queue workers, and each concurrent render is a separate OS process competing for memory — WeasyPrint on a complex document with many images can spike several hundred MB. Keep renders off the web request path; use a dedicated queue with its own worker pool, a concurrency cap, and a per-document timeout set at the worker layer, not in the library. Large batches should be chunked, and outputs pushed to object storage rather than streamed repeatedly.
Failure modes. Binary missing or wrong version (fails fast, easy to detect — add a boot health check). Non-zero exit with a WeasyPrint traceback surfaced only in stderr (the package logs it; ensure our log channel captures it with the job ID). Timeouts on pathological templates (bounded by worker timeout; alert on rate). Font fallback producing visually wrong but successful PDFs (only caught by golden-image comparison, not by exceptions — this is the silent failure and the one worth testing). Temp-file or disk exhaustion on high-volume bursts. SSRF attempts if URL input ever comes from users — keep scheme allow-lists at defaults and validate user-supplied URLs before they reach the wrapper.
Ramp-up. Small: one engineer can be productive in a day, because the API is six methods. The learning curve is CSS Paged Media and font/paged-layout debugging, not PHP. Plan for one engineer to become the designated owner of both the image and the templates — split ownership between "app dev" and "infra" is a predictable failure with this package.
How can I help you explore Laravel packages today?