Product Decisions This Supports
- Engine-agnostic document output as a first-class capability.
print-contracts is the interface layer of the rekalogika print suite: it defines what a "document to PDF" operation looks like (source document, conversion, print job semantics) without committing to a renderer. Adopting it makes "which PDF engine" a configuration decision (DomPDF, mPDF, wkhtmltopdf, headless Chromium, Gotenberg, a hosted API) rather than an architectural one.
- Build vs. buy, with an exit ramp. If we build document generation in-house, this contract is the seam that keeps switching costs low; if we buy a vendor, the vendor gets wrapped behind the same interface so procurement can renegotiate without touching domain code.
- Roadmap sequencing under uncertainty. It lets us ship the document feature now and defer the engine choice until we have volume, fidelity, and latency data — instead of blocking the roadmap on a renderer bake-off.
- Feature scoping for anything that produces a printable artifact: invoices, account statements, shipping labels, packing slips, compliance filings, customer-facing reports, batch statement runs. If "user needs a PDF," this is the abstraction under it.
- Forcing function for non-functional requirements. The contracts encode concepts like print job lifecycle, paper size/orientation, and conversion targets — writing against them surfaces requirements (sync download vs. queued batch, job status polling, per-tenant branding) early, while they're still cheap to decide.
- Testability as a product-quality lever. Document workflows can be unit-tested against fakes instead of rendering real PDFs in CI, which shortens feedback loops and reduces flaky test suites — a real productivity line item, not just hygiene.
- Enables multi-engine strategies. Per-tenant engines, premium-fidelity rendering for a paid tier, or fallback-to-secondary-engine when the primary fails all become policy in config rather than a rewrite.
When to Consider This Package
Consider it when:
- You're on PHP/Laravel and expect to emit documents, and you realistically expect to change engines at least once (quality complaints, cost, infrastructure migration, or a vendor relationship ending).
- You want to keep the domain layer free of engine-specific types so you can test, mock, and swap.
- You have several document types with different fidelity needs, or a roadmap that includes both interactive downloads and high-volume batch generation.
- You want the option of running a hosted PDF service later without rewriting call sites.
Look elsewhere when:
- You need working PDF output now. This package is abstractions only — no renderer. It must be paired with an implementation from the same suite. If the team is evaluating on "does it produce a PDF today," the answer is no, and that's by design.
- Adoption risk matters more than optionality. Zero stars, zero dependents, a single maintainer, and a READ-ONLY split repo (contributions flow to the parent monorepo) mean limited community validation. Treat it as a small OSS dependency with a bus-factor of one. Mitigations: vendor the code, pin the version, or reimplement the handful of interfaces yourself in a day.
- Your team is small and prefers one concrete library. A thin wrapper around a single engine may be less total surface area than an abstraction layer plus an adapter.
- You need commercial support, SLAs, or an indemnified supplier — this is MIT community software with no backing entity.
- Non-PHP stacks, or teams without Composer/dependency-review discipline.
Practical guidance: the cost of adoption is genuinely low (it's interfaces), so the decision is asymmetric — cheap to try, cheap to remove. Cap the exposure by isolating it behind an application-level port of our own rather than importing it across the codebase.
How to Pitch It (Stakeholders)
To executives — cheap insurance against vendor lock-in. This is not a rendering engine; it's a set of agreements about what "make a PDF" means in our codebase. Adopting it costs roughly a day and buys us the ability to replace our PDF provider — for cost, quality, or reliability reasons — without a multi-week refactor. In procurement terms, it's the technical clause that keeps our document vendor replaceable.
To engineering — an interface package, not a framework. It defines contracts only; you still pick and wire a concrete implementation. It keeps engine-specific code confined to adapters, makes document generation testable with fakes, and avoids leaking third-party types into the domain model. Caveats to state plainly: zero stars, zero known dependents, single maintainer, read-only split repo — so pin the version and wrap it in our own port. If the dependency becomes a liability, the interfaces are small enough to reimplement in-house.
Recommendation: approve a time-boxed spike (1–2 days) to implement one document type end-to-end against the contract with a single engine. Success criteria: domain code contains no engine-specific types, and swapping to a second engine is a config change only. If the spike holds, adopt; if not, we've lost two days and gained a clear picture of our own requirements.