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

Print Contracts Laravel Package

rekalogika/print-contracts

rekalogika/print-contracts provides PHP interfaces and contracts for printing workflows, covering print jobs, documents, media, and printer services. It gives libraries and frameworks a shared, implementation-agnostic API for integrating any compatible print backend.

View on GitHub
Deep Wiki
Context7

Getting Started

rekalogika/print-contracts is a thin, framework-agnostic set of PHP abstractions (interfaces and value objects) for turning domain objects into PDFs and routing them to printers. It ships no runtime driver — it is the seam you code against, and a separate implementation package (e.g. rekalogika/print-dompdf, rekalogika/print-wkhtmltopdf, or a Gotenberg client) plugs in behind it.

Install both the contracts and a driver in one go:

composer require rekalogika/print-contracts rekalogika/print-dompdf

Where to look first:

  1. src/PrinterInterface.php — the entry point. Expect a single method in the shape of print(object $printable): string (binary PDF) or an equivalent returning a result/stream object.
  2. src/PrintableInterface.php (or a Printable trait) — the contract your own models/view models implement to declare what gets rendered.
  3. Any value objects for paper size, orientation, options — read these before you touch the driver, because options are usually passed through opaquely.

First use case — render a Laravel invoice to a streamed download:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(PrinterInterface::class, function () {
        return new DompdfPrinter(config('services.print'));
    });
}
// app/Models/Invoice.php  (implements the package's Printable contract)
class Invoice implements PrintableInterface
{
    public function getPrintableContent(): string
    {
        return view('invoices.pdf', ['invoice' => $this])->render();
    }
}
// app/Http/Controllers/InvoiceController.php
public function download(Invoice $invoice, PrinterInterface $printer)
{
    $pdf = $printer->print($invoice);

    return response($pdf, 200, [
        'Content-Type'        => 'application/pdf',
        'Content-Disposition' => 'attachment; filename="invoice-'.$invoice->id.'.pdf"',
    ]);
}

That's the full loop: implement Printable, bind one PrinterInterface implementation, inject the interface. Everything else (HTML-to-PDF engine, paper size, margins) is the driver's concern.

Implementation Patterns

Code against the interface, never the driver class. The whole point of a contracts package is substitutability. Controllers, jobs, and listeners should type-hint PrinterInterface. Swap DompdfPrinter for a Gotenberg-backed implementation in a config toggle and nothing downstream changes.

Wrap Blade-rendered HTML in a dedicated printable value object. Don't implement PrintableInterface directly on Eloquent models — it welds your domain layer to a rendering concern and to the package. A small DTO per document is cleaner and testable in isolation:

final class InvoicePdf implements PrintableInterface
{
    public function __construct(public readonly Invoice $invoice) {}

    public function getPrintableContent(): string
    {
        return view('pdf.invoice', ['invoice' => $this->invoice])->render();
    }
}

Queue PDF generation for anything non-trivial. Wkhtmltopdf and headless Chrome are slow. Follow the synchronous interface for small docs, but for bulk (100 statements, monthly run) dispatch a job that injects the printer, calls print(), writes to Storage, and emails a link. The contracts package doesn't care whether you're in a request or a worker.

Decorator pattern for cross-cutting concerns. Because everything is an interface, you can wrap the real printer with logging, timing, caching, or an audit trail without touching the driver:

final class LoggingPrinter implements PrinterInterface
{
    public function __construct(
        private PrinterInterface $inner,
        private LoggerInterface $log,
    ) {}

    public function print(object $printable): string
    {
        $start = microtime(true);
        $pdf   = $this->inner->print($printable);
        $this->log->info('PDF generated', ['ms' => (int) ((microtime(true) - $start) * 1000)]);
        return $pdf;
    }
}

Bind the real one as the concrete class, then bind PrinterInterface to the decorator.

Multiple printers via naming. If you have both a fast in-process engine (Dompdf, for previews) and a high-fidelity remote one (Prince, a hosted service), register two container bindings keyed by context — print.fast and print.high-fidelity — and resolve the right one in a factory. Don't try to make one driver do both.

Route through a service layer for testability. A DocumentService that depends on PrinterInterface gives you one place to stub in tests and one place to add authorization, retention policy, and watermarks.

Integration tip — response types. The contract likely returns a PHP binary string. For large PDFs, prefer streaming to a temporary file (Storage::disk('local')) and response()->streamDownload() rather than holding tens of MB in memory.

Gotchas and Tips

  • It's contracts only. If composer require rekalogika/print-contracts alone is installed, container resolution of PrinterInterface will fail. You must add an implementation package and bind it. The zero stars / zero dependents is a signal: there is no ready Laravel service provider, so expect to write 10–15 lines of AppServiceProvider wiring yourself.

  • Symfony-shaped, not Laravel-shaped. Rekalogika's ecosystem targets Symfony bundles with autowired services. A Laravel app has no auto-wiring for these — you register singleton bindings manually. Don't expect php artisan vendor:publish for config.

  • Interface churn. Contracts packages rev freely in 0.x. Pin the constraints ("rekalogika/print-contracts": "~0.1.0") and read the UPGRADE.md on every minor bump before composer update.

  • The printable's render method name matters. If your implementation returns a file path versus HTML versus markup object, the driver's contract determines whether it's render(), getContent(), or __toString(). Check the interface signature against the driver version — mismatches between contracts version and driver version are the number-one reason a "working" example throws a TypeError.

  • Debugging blank PDFs. Almost always whitespace from a stray @php ... @endphp emitting a BOM, or a view()->render() returning empty because the DTO passed the wrong data. Log strlen($pdf) before the response; a valid empty PDF is still ~1KB, whereas an empty string is a red flag.

  • Paper size and DPI are driver properties, not contract properties. The contracts package may carry paper-size value objects, but margins, DPI, and header/footer templates are implementation details of whatever engine you chose. Configuring them on the abstraction wastes time — drop into the concrete driver's config array.

  • Timeouts in workers. Gotenberg/Chrome-backed drivers hang for 30–60s under load. Set an explicit timeout in the HTTP client your driver uses; the contracts layer won't do it for you. A queue worker's default 60s --timeout is often too short for bulk PDF runs.

  • Testing seam. Because PrinterInterface is one method, a FakePrinter returning a canned 1-page PDF is trivial. Assert on the printable (which view was rendered, with what data) rather than the binary bytes — binary PDF assertions are brittle.

  • Extension points. If you need a new rendering capability (SVG inlining, signed PDFs, custom fonts), don't fork the contracts package — extend at the driver level or implement a decorator around PrinterInterface. The contracts are intentionally minimal; fighting that is a symptom you've picked the wrong layer to solve the problem.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels