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
Agents
sylius/pdf-generation-bundle
claude-code
cursor
symfony
pdf

A Symfony bundle providing context-aware PDF generation with swappable adapter backends.

Install
php artisan boost:add-skill sylius/pdf-generation-bundle

Save this content to: AGENTS.md

# SyliusPdfGenerationBundle - Agent Guide

A Symfony bundle providing context-aware PDF generation with swappable adapter backends.

## What This Bundle Does

Converts HTML (or Twig templates) into PDF files. The caller picks a **context** (e.g. `invoice`, `coupon`), and the bundle routes the request to the correct **adapter** (e.g. KnpSnappy/wkhtmltopdf, Dompdf). Each context can use a different adapter with different options.

## Key Interfaces

```
TwigToPdfRendererInterface::render(string $template, array $parameters = [], string $context = 'default'): string
```
High-level entry point. Renders a Twig template to HTML, then delegates to HtmlToPdfRenderer.

```
HtmlToPdfRendererInterface::render(string $html, string $context = 'default'): string
```
Takes raw HTML, selects the adapter for the given context from a ServiceLocator, and returns PDF bytes.

```
PdfGenerationAdapterInterface::generate(string $html): string
```
Low-level adapter interface. Takes HTML, returns PDF bytes. Adapters are context-unaware.

```
OptionsProcessorInterface::process(object $generator, string $context = 'default'): void
```
Configures the underlying PDF generator object (e.g. `Knp\Snappy\AbstractGenerator` or `Dompdf\Dompdf`) before PDF generation. Multiple processors run in sequence via `CompositeOptionsProcessor`.

```
GeneratorProviderInterface::get(?string $context = null): object
```
Provides the underlying generator instance (e.g. a `Knp\Snappy\Pdf` or `Dompdf\Dompdf`). Used by adapters to get a fresh or shared generator per request.

```
PdfStorageInterface::save(PdfFile $file): void
PdfStorageInterface::remove(string $filename): void
PdfStorageInterface::has(string $filename): bool
PdfStorageInterface::get(string $filename): PdfFile
PdfStorageInterface::resolveLocalPath(string $filename): string
```
Context-unaware storage contract. Each instance handles one location. `resolveLocalPath()` returns an absolute local filesystem path — for `SymfonyFilesystemPdfStorage` this is the existing path; for Flysystem/Gaufrette it lazily materializes a local copy in the configured `local_cache_directory`. Implementations: `FlysystemPdfStorage` (default), `SymfonyFilesystemPdfStorage`, `GaufrettePdfStorage`.

```
PdfFileManagerInterface::save(PdfFile $file, string $context = 'default'): void
PdfFileManagerInterface::remove(string $filename, string $context = 'default'): void
PdfFileManagerInterface::has(string $filename, string $context = 'default'): bool
PdfFileManagerInterface::get(string $filename, string $context = 'default'): PdfFile
PdfFileManagerInterface::resolveLocalPath(string $filename, string $context = 'default'): string
```
Manages PDF file persistence. `PdfFileManager` delegates to per-context `PdfStorageInterface` instances via a ServiceLocator.

```
PdfFileGeneratorInterface::generate(string $filename, string $content, string $context = 'default'): PdfFile
```
Wraps already-rendered PDF content into a `PdfFile` model and saves it via `PdfFileManagerInterface`. Does not perform rendering itself.

## Architecture

### Request Flow

```
TwigToPdfRenderer
  -> renders Twig template to HTML
  -> HtmlToPdfRenderer
       -> picks adapter from ServiceLocator by context name
       -> Adapter (e.g. KnpSnappyAdapter)
            -> GeneratorProviderRegistry::get(adapterType, context)
                 returns underlying generator (Knp\Snappy\Pdf or Dompdf\Dompdf)
            -> CompositeOptionsProcessor::process(generator, context)
                 runs default processors first, then context-specific ones
            -> generator produces PDF bytes
       -> returns PDF string
```

### Storage Flow

```
PdfFileManager (context-aware)
  -> ServiceLocator keyed by context name
       -> PdfStorageInterface instance per context
            FlysystemPdfStorage    (league/flysystem-bundle — optional)
            SymfonyFilesystemPdfStorage  (symfony/filesystem — default, hard dep)
            GaufrettePdfStorage    (knp-gaufrette-bundle — optional)
```

### Options Processing Chain

Each adapter type has a `CompositeOptionsProcessor` that groups processors by context:

```
CompositeOptionsProcessor (per adapter type)
  'default'           -> [ProcessorA, ProcessorB]     # always run
  'sylius_invoicing'  -> [ProcessorC]                  # run after defaults, only for this context
```

When processing context `sylius_invoicing`, execution order is: ProcessorA, ProcessorB, ProcessorC.
When processing context `default`, only: ProcessorA, ProcessorB.

### DI Wiring

**Extension phase** (`SyliusPdfGenerationExtension::load`):
- Processes config, loads `config/services.php` (core services)
- For each context (default + named contexts):
  - If adapter is built-in (`knp_snappy`, `dompdf`): loads adapter service file once, creates a `ChildDefinition` for the context, registers an options processor tagged `sylius_pdf_generation.options_processor`
  - If adapter is unknown: stores in `.sylius_pdf_generation.deferred_adapter_contexts` parameter (dot-prefixed = internal, auto-removed by Symfony)
- Builds per-context `PdfStorageInterface` instances based on `storage` config (validates required classes for each storage type, throws `LogicException` with `composer require` hint); wires into `PdfFileManager`'s ServiceLocator
- Wires the adapter references into `HtmlToPdfRenderer`'s ServiceLocator

**Compiler passes** (in registration order):
1. `RegisterKnpSnappyPrototypePass` - clones the `knp_snappy.pdf` definition as a non-shared prototype so the generator provider can create fresh instances per call
2. `RegisterGeneratorProvidersPass` - collects `sylius_pdf_generation.generator_provider` tagged services into the `GeneratorProviderRegistry`
3. `RegisterOptionsProcessorsPass` - collects all `sylius_pdf_generation.options_processor` tagged services, groups by adapter+context, sorts by priority, builds `CompositeOptionsProcessor` per adapter
4. `RegisterPdfGenerationAdaptersPass` - resolves deferred (custom) adapters by matching `sylius_pdf_generation.adapter` tags to context names, merges into the ServiceLocator

## Configuration

```yaml
sylius_pdf_generation:
    gotenberg:
        base_url: 'http://localhost:3000'  # Gotenberg server URL (default)
    default:
        adapter: knp_snappy               # knp_snappy | dompdf | gotenberg | <custom_key>
        storage:                           # storage defaults for all contexts
            type: flysystem                # flysystem | filesystem | gaufrette
            filesystem: 'default.storage'  # service ID (flysystem/gaufrette)
            prefix: 'pdf'                  # path prefix (flysystem/gaufrette)
            directory: ~                   # local path (filesystem only)
            local_cache_directory: ~       # local cache for resolveLocalPath() (flysystem/gaufrette)
    contexts:
        invoice:
            adapter: dompdf
            storage:
                type: flysystem
                filesystem: 's3.storage'
                prefix: 'invoices'
                local_cache_directory: '%kernel.project_dir%/var/pdf_cache'
        receipt:
            adapter: knp_snappy
            storage:
                type: filesystem
                directory: '%kernel.project_dir%/private/receipts'
```

- `default` is always present; name `default` is forbidden under `contexts`
- Each entry has two fields: `adapter` (string) and `storage` (array|null)
- `storage` cascades: default -> context override
- Built-in adapters (`knp_snappy`, `dompdf`, `gotenberg`) require their packages to be installed; a `LogicException` is thrown otherwise
- Unknown adapter names are deferred to the compiler pass
- Adapter-specific options are not configured via YAML; use custom `OptionsProcessorInterface` implementations instead (see Extension Points)

## Directory Structure

```
src/
  Core/
    Adapter/PdfGenerationAdapterInterface.php
    Attribute/
      AsPdfGenerationAdapter.php      # marks custom adapter classes
      AsPdfOptionsProcessor.php       # marks custom options processors
      AsPdfGeneratorProvider.php      # marks custom generator providers
    Generator/
      PdfFileGenerator.php            # wraps content into PdfFile + saves via manager
      PdfFileGeneratorInterface.php
    Filesystem/
      Manager/
        PdfFileManager.php            # delegates to per-context PdfStorageInterface via ServiceLocator
        PdfFileManagerInterface.php
      Storage/
        PdfStorageInterface.php       # context-unaware storage contract
    Model/PdfFile.php                 # simple model (filename + content + mutable storagePath)
    Processor/
      CompositeOptionsProcessor.php   # chains processors by context
      OptionsProcessorInterface.php
    Provider/GeneratorProviderInterface.php
    Registry/
      GeneratorProviderRegistry.php   # keyed by adapter type + context
      GeneratorProviderRegistryInterface.php
    Renderer/
      HtmlToPdfRenderer.php           # selects adapter by context
      HtmlToPdfRendererInterface.php
      TwigToPdfRenderer.php           # Twig -> HTML -> HtmlToPdfRenderer
      TwigToPdfRendererInterface.php
  Filesystem/
    Flysystem/
      FlysystemPdfStorage.php         # Flysystem storage (default, hard dep)
    Symfony/
      SymfonyFilesystemPdfStorage.php # Symfony Filesystem storage (optional)
    Gaufrette/
      GaufrettePdfStorage.php         # Gaufrette storage (optional)
  Adapter/
    KnpSnappy/
      KnpSnappyAdapter.php           # wraps knplabs/knp-snappy-bundle
      KnpSnappyGeneratorProvider.php  # creates fresh Knp\Snappy\Pdf instances via prototype factory
      KnpSnappyOptionsProcessor.php   # sets snappy options + allowed_files
    Dompdf/
      DompdfAdapter.php               # wraps dompdf/dompdf
      DompdfGeneratorProvider.php     # creates fresh Dompdf\Dompdf per call
    Gotenberg/
      GotenbergAdapter.php            # wraps gotenberg/gotenberg-php
      GotenbergGeneratorProvider.php  # creates fresh ChromiumPdf builder per call
  DependencyInjection/
    Compiler/
      RegisterKnpSnappyPrototypePass.php       # clones knp_snappy.pdf as non-shared prototype
      RegisterGeneratorProvidersPass.php       # populates provider registry
      RegisterOptionsProcessorsPass.php       # builds composite processors
      RegisterPdfGenerationAdaptersPass.php   # resolves custom adapters
    Configuration.php
    SyliusPdfGenerationExtension.php
  SyliusPdfGenerationBundle.php
config/
  services.php                # core services (HtmlToPdfRenderer, TwigToPdfRenderer, manager, registry)
  adapter/knp_snappy.php      # KnpSnappy adapter + processor + provider (abstract)
  adapter/dompdf.php          # Dompdf adapter + processor + provider (abstract)
  adapter/gotenberg.php       # Gotenberg adapter + provider (abstract)
```

## Extension Points

### 1. Custom Adapter

For integrating a PDF library not bundled with this package.

**Via attribute (recommended):**
```php
use Sylius\PdfGenerationBundle\Core\Adapter\PdfGenerationAdapterInterface;
use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfGenerationAdapter;

#[AsPdfGenerationAdapter('my_adapter')]
final class MyAdapter implements PdfGenerationAdapterInterface
{
    public function generate(string $html): string { /* ... */ }
}
```

**Via service tag:**
```yaml
App\Pdf\MyAdapter:
    tags:
        - { name: 'sylius_pdf_generation.adapter', key: 'my_adapter' }
```

Then reference in config:
```yaml
sylius_pdf_generation:
    default:
        adapter: my_adapter
```

Custom adapters are user-managed services. The bundle does not create instances or pass options to them.

### 2. Custom Options Processor

For modifying the underlying generator's configuration before PDF generation. Useful for adding allowed files, setting paper size, or injecting security options.

**Via attribute:**
```php
use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfOptionsProcessor;
use Sylius\PdfGenerationBundle\Core\Processor\OptionsProcessorInterface;

#[AsPdfOptionsProcessor(adapter: 'knp_snappy', context: 'invoice', priority: 10)]
final class InvoiceOptionsProcessor implements OptionsProcessorInterface
{
    public function process(object $generator, string $context = 'default'): void
    {
        // $generator is Knp\Snappy\AbstractGenerator for knp_snappy
        $generator->setOption('page-size', 'A4');
    }
}
```

**Via service tag:**
```yaml
App\Pdf\InvoiceOptionsProcessor:
    tags:
        - { name: 'sylius_pdf_generation.options_processor', adapter: 'knp_snappy', context: 'invoice', priority: 10 }
```

Tag attributes:
- `adapter` (required): which adapter type this processor applies to
- `context` (optional, default: `'default'`): which context; omit to apply to all contexts using this adapter
- `priority` (optional, default: `0`): higher = runs first

### 3. Custom Generator Provider

For controlling how the underlying generator object is created or reused per context.

**Via attribute:**
```php
use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfGeneratorProvider;
use Sylius\PdfGenerationBundle\Core\Provider\GeneratorProviderInterface;

#[AsPdfGeneratorProvider(adapter: 'knp_snappy', context: 'invoice')]
final class InvoiceGeneratorProvider implements GeneratorProviderInterface
{
    public function get(?string $context = null): object
    {
        // Return a custom-configured Knp\Snappy\Pdf instance
    }
}
```

**Via service tag:**
```yaml
App\Pdf\InvoiceGeneratorProvider:
    tags:
        - { name: 'sylius_pdf_generation.generator_provider', adapter: 'knp_snappy', context: 'invoice' }
```

### 4. Replacing Core Services

All core services are aliased by interface. Override via Symfony's service decoration:

```yaml
Sylius\PdfGenerationBundle\Core\Renderer\HtmlToPdfRendererInterface:     # alias -> sylius_pdf_generation.renderer.html
Sylius\PdfGenerationBundle\Core\Renderer\TwigToPdfRendererInterface:     # alias -> sylius_pdf_generation.renderer.twig
Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface:  # alias -> sylius_pdf_generation.manager
Sylius\PdfGenerationBundle\Core\Adapter\PdfGenerationAdapterInterface:   # alias -> sylius_pdf_generation.adapter.default
Sylius\PdfGenerationBundle\Core\Registry\GeneratorProviderRegistryInterface:  # alias -> sylius_pdf_generation.registry.generator_provider
```

## Built-in Adapters

### KnpSnappy (`knp_snappy`)
- Requires: `knplabs/knp-snappy-bundle` ^1.10
- Uses wkhtmltopdf binary under the hood
- `KnpSnappyOptionsProcessor` sets options on `AbstractGenerator` and resolves `allowed_files` via `FileLocator`
- `KnpSnappyGeneratorProvider` creates a fresh `Knp\Snappy\Pdf` instance per call via a prototype factory closure (backed by `RegisterKnpSnappyPrototypePass`)

### Dompdf (`dompdf`)
- Requires: `dompdf/dompdf` ^3.1
- Pure PHP, no external binary
- `DompdfGeneratorProvider` creates fresh `Dompdf\Dompdf` instances

### Gotenberg (`gotenberg`)
- Requires: `gotenberg/gotenberg-php` ^2.0
- Uses Gotenberg's Docker-based API with headless Chromium for HTML-to-PDF conversion
- `GotenbergGeneratorProvider` creates a fresh `ChromiumPdf` builder per call, connecting to the Gotenberg server at the configured `base_url`
- Adapter flow: gets `ChromiumPdf` builder → processes options → calls `$builder->html(Stream::string(...))` to build the request → sends via `Gotenberg::send()` → returns PDF bytes
- Configure the Gotenberg server URL via `sylius_pdf_generation.gotenberg.base_url` (defaults to `http://localhost:3000`)

All adapters are optional dependencies. Configuring an adapter whose package is not installed throws a `LogicException` with a `composer require` hint.

## Built-in Storage Backends

### Flysystem (`flysystem`)
- Requires: `league/flysystem-bundle` ^3.0 (optional)
- Constructor: `FilesystemOperator $filesystem, LocalFilesystem $localFilesystem, string $prefix = '', ?string $localCacheDirectory = null`
- Paths use `/` separator (Flysystem normalizes), sets `storagePath` on saved/retrieved files
- `resolveLocalPath()` requires `local_cache_directory` config; lazily fetches and caches locally, invalidates on save/remove

### Symfony Filesystem (`filesystem`)
- Requires: `symfony/filesystem` (hard dependency)
- Constructor: `Filesystem $filesystem, string $directory`
- Uses `DIRECTORY_SEPARATOR` for paths, sets `storagePath` on saved/retrieved files
- `resolveLocalPath()` delegates to internal `resolvePath()` — file is already local

### Gaufrette (`gaufrette`)
- Requires: `knplabs/knp-gaufrette-bundle` (optional)
- Constructor: `GaufretteFilesystem $filesystem, LocalFilesystem $localFilesystem, string $prefix = '', ?string $localCacheDirectory = null`
- Paths use `/` separator, sets `storagePath` on saved/retrieved files
- `resolveLocalPath()` requires `local_cache_directory` config; lazily fetches and caches locally, invalidates on save/remove

## Development

```bash
vendor/bin/phpunit                              # tests
vendor/bin/phpstan analyse -c phpstan.dist.neon  # static analysis
vendor/bin/ecs check                            # coding standards
```

Tests skip gracefully when optional dependencies are missing via `setUpBeforeClass()` + `markTestSkipped()`.

SyliusPdfGenerationBundle - Agent Guide

A Symfony bundle providing context-aware PDF generation with swappable adapter backends.

What This Bundle Does

Converts HTML (or Twig templates) into PDF files. The caller picks a context (e.g. invoice, coupon), and the bundle routes the request to the correct adapter (e.g. KnpSnappy/wkhtmltopdf, Dompdf). Each context can use a different adapter with different options.

Key Interfaces

TwigToPdfRendererInterface::render(string $template, array $parameters = [], string $context = 'default'): string

High-level entry point. Renders a Twig template to HTML, then delegates to HtmlToPdfRenderer.

HtmlToPdfRendererInterface::render(string $html, string $context = 'default'): string

Takes raw HTML, selects the adapter for the given context from a ServiceLocator, and returns PDF bytes.

PdfGenerationAdapterInterface::generate(string $html): string

Low-level adapter interface. Takes HTML, returns PDF bytes. Adapters are context-unaware.

OptionsProcessorInterface::process(object $generator, string $context = 'default'): void

Configures the underlying PDF generator object (e.g. Knp\Snappy\AbstractGenerator or Dompdf\Dompdf) before PDF generation. Multiple processors run in sequence via CompositeOptionsProcessor.

GeneratorProviderInterface::get(?string $context = null): object

Provides the underlying generator instance (e.g. a Knp\Snappy\Pdf or Dompdf\Dompdf). Used by adapters to get a fresh or shared generator per request.

PdfStorageInterface::save(PdfFile $file): void
PdfStorageInterface::remove(string $filename): void
PdfStorageInterface::has(string $filename): bool
PdfStorageInterface::get(string $filename): PdfFile
PdfStorageInterface::resolveLocalPath(string $filename): string

Context-unaware storage contract. Each instance handles one location. resolveLocalPath() returns an absolute local filesystem path — for SymfonyFilesystemPdfStorage this is the existing path; for Flysystem/Gaufrette it lazily materializes a local copy in the configured local_cache_directory. Implementations: FlysystemPdfStorage (default), SymfonyFilesystemPdfStorage, GaufrettePdfStorage.

PdfFileManagerInterface::save(PdfFile $file, string $context = 'default'): void
PdfFileManagerInterface::remove(string $filename, string $context = 'default'): void
PdfFileManagerInterface::has(string $filename, string $context = 'default'): bool
PdfFileManagerInterface::get(string $filename, string $context = 'default'): PdfFile
PdfFileManagerInterface::resolveLocalPath(string $filename, string $context = 'default'): string

Manages PDF file persistence. PdfFileManager delegates to per-context PdfStorageInterface instances via a ServiceLocator.

PdfFileGeneratorInterface::generate(string $filename, string $content, string $context = 'default'): PdfFile

Wraps already-rendered PDF content into a PdfFile model and saves it via PdfFileManagerInterface. Does not perform rendering itself.

Architecture

Request Flow

TwigToPdfRenderer
  -> renders Twig template to HTML
  -> HtmlToPdfRenderer
       -> picks adapter from ServiceLocator by context name
       -> Adapter (e.g. KnpSnappyAdapter)
            -> GeneratorProviderRegistry::get(adapterType, context)
                 returns underlying generator (Knp\Snappy\Pdf or Dompdf\Dompdf)
            -> CompositeOptionsProcessor::process(generator, context)
                 runs default processors first, then context-specific ones
            -> generator produces PDF bytes
       -> returns PDF string

Storage Flow

PdfFileManager (context-aware)
  -> ServiceLocator keyed by context name
       -> PdfStorageInterface instance per context
            FlysystemPdfStorage    (league/flysystem-bundle — optional)
            SymfonyFilesystemPdfStorage  (symfony/filesystem — default, hard dep)
            GaufrettePdfStorage    (knp-gaufrette-bundle — optional)

Options Processing Chain

Each adapter type has a CompositeOptionsProcessor that groups processors by context:

CompositeOptionsProcessor (per adapter type)
  'default'           -> [ProcessorA, ProcessorB]     # always run
  'sylius_invoicing'  -> [ProcessorC]                  # run after defaults, only for this context

When processing context sylius_invoicing, execution order is: ProcessorA, ProcessorB, ProcessorC. When processing context default, only: ProcessorA, ProcessorB.

DI Wiring

Extension phase (SyliusPdfGenerationExtension::load):

  • Processes config, loads config/services.php (core services)
  • For each context (default + named contexts):
    • If adapter is built-in (knp_snappy, dompdf): loads adapter service file once, creates a ChildDefinition for the context, registers an options processor tagged sylius_pdf_generation.options_processor
    • If adapter is unknown: stores in .sylius_pdf_generation.deferred_adapter_contexts parameter (dot-prefixed = internal, auto-removed by Symfony)
  • Builds per-context PdfStorageInterface instances based on storage config (validates required classes for each storage type, throws LogicException with composer require hint); wires into PdfFileManager's ServiceLocator
  • Wires the adapter references into HtmlToPdfRenderer's ServiceLocator

Compiler passes (in registration order):

  1. RegisterKnpSnappyPrototypePass - clones the knp_snappy.pdf definition as a non-shared prototype so the generator provider can create fresh instances per call
  2. RegisterGeneratorProvidersPass - collects sylius_pdf_generation.generator_provider tagged services into the GeneratorProviderRegistry
  3. RegisterOptionsProcessorsPass - collects all sylius_pdf_generation.options_processor tagged services, groups by adapter+context, sorts by priority, builds CompositeOptionsProcessor per adapter
  4. RegisterPdfGenerationAdaptersPass - resolves deferred (custom) adapters by matching sylius_pdf_generation.adapter tags to context names, merges into the ServiceLocator

Configuration

sylius_pdf_generation:
    gotenberg:
        base_url: 'http://localhost:3000'  # Gotenberg server URL (default)
    default:
        adapter: knp_snappy               # knp_snappy | dompdf | gotenberg | <custom_key>
        storage:                           # storage defaults for all contexts
            type: flysystem                # flysystem | filesystem | gaufrette
            filesystem: 'default.storage'  # service ID (flysystem/gaufrette)
            prefix: 'pdf'                  # path prefix (flysystem/gaufrette)
            directory: ~                   # local path (filesystem only)
            local_cache_directory: ~       # local cache for resolveLocalPath() (flysystem/gaufrette)
    contexts:
        invoice:
            adapter: dompdf
            storage:
                type: flysystem
                filesystem: 's3.storage'
                prefix: 'invoices'
                local_cache_directory: '%kernel.project_dir%/var/pdf_cache'
        receipt:
            adapter: knp_snappy
            storage:
                type: filesystem
                directory: '%kernel.project_dir%/private/receipts'
  • default is always present; name default is forbidden under contexts
  • Each entry has two fields: adapter (string) and storage (array|null)
  • storage cascades: default -> context override
  • Built-in adapters (knp_snappy, dompdf, gotenberg) require their packages to be installed; a LogicException is thrown otherwise
  • Unknown adapter names are deferred to the compiler pass
  • Adapter-specific options are not configured via YAML; use custom OptionsProcessorInterface implementations instead (see Extension Points)

Directory Structure

src/
  Core/
    Adapter/PdfGenerationAdapterInterface.php
    Attribute/
      AsPdfGenerationAdapter.php      # marks custom adapter classes
      AsPdfOptionsProcessor.php       # marks custom options processors
      AsPdfGeneratorProvider.php      # marks custom generator providers
    Generator/
      PdfFileGenerator.php            # wraps content into PdfFile + saves via manager
      PdfFileGeneratorInterface.php
    Filesystem/
      Manager/
        PdfFileManager.php            # delegates to per-context PdfStorageInterface via ServiceLocator
        PdfFileManagerInterface.php
      Storage/
        PdfStorageInterface.php       # context-unaware storage contract
    Model/PdfFile.php                 # simple model (filename + content + mutable storagePath)
    Processor/
      CompositeOptionsProcessor.php   # chains processors by context
      OptionsProcessorInterface.php
    Provider/GeneratorProviderInterface.php
    Registry/
      GeneratorProviderRegistry.php   # keyed by adapter type + context
      GeneratorProviderRegistryInterface.php
    Renderer/
      HtmlToPdfRenderer.php           # selects adapter by context
      HtmlToPdfRendererInterface.php
      TwigToPdfRenderer.php           # Twig -> HTML -> HtmlToPdfRenderer
      TwigToPdfRendererInterface.php
  Filesystem/
    Flysystem/
      FlysystemPdfStorage.php         # Flysystem storage (default, hard dep)
    Symfony/
      SymfonyFilesystemPdfStorage.php # Symfony Filesystem storage (optional)
    Gaufrette/
      GaufrettePdfStorage.php         # Gaufrette storage (optional)
  Adapter/
    KnpSnappy/
      KnpSnappyAdapter.php           # wraps knplabs/knp-snappy-bundle
      KnpSnappyGeneratorProvider.php  # creates fresh Knp\Snappy\Pdf instances via prototype factory
      KnpSnappyOptionsProcessor.php   # sets snappy options + allowed_files
    Dompdf/
      DompdfAdapter.php               # wraps dompdf/dompdf
      DompdfGeneratorProvider.php     # creates fresh Dompdf\Dompdf per call
    Gotenberg/
      GotenbergAdapter.php            # wraps gotenberg/gotenberg-php
      GotenbergGeneratorProvider.php  # creates fresh ChromiumPdf builder per call
  DependencyInjection/
    Compiler/
      RegisterKnpSnappyPrototypePass.php       # clones knp_snappy.pdf as non-shared prototype
      RegisterGeneratorProvidersPass.php       # populates provider registry
      RegisterOptionsProcessorsPass.php       # builds composite processors
      RegisterPdfGenerationAdaptersPass.php   # resolves custom adapters
    Configuration.php
    SyliusPdfGenerationExtension.php
  SyliusPdfGenerationBundle.php
config/
  services.php                # core services (HtmlToPdfRenderer, TwigToPdfRenderer, manager, registry)
  adapter/knp_snappy.php      # KnpSnappy adapter + processor + provider (abstract)
  adapter/dompdf.php          # Dompdf adapter + processor + provider (abstract)
  adapter/gotenberg.php       # Gotenberg adapter + provider (abstract)

Extension Points

1. Custom Adapter

For integrating a PDF library not bundled with this package.

Via attribute (recommended):

use Sylius\PdfGenerationBundle\Core\Adapter\PdfGenerationAdapterInterface;
use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfGenerationAdapter;

#[AsPdfGenerationAdapter('my_adapter')]
final class MyAdapter implements PdfGenerationAdapterInterface
{
    public function generate(string $html): string { /* ... */ }
}

Via service tag:

App\Pdf\MyAdapter:
    tags:
        - { name: 'sylius_pdf_generation.adapter', key: 'my_adapter' }

Then reference in config:

sylius_pdf_generation:
    default:
        adapter: my_adapter

Custom adapters are user-managed services. The bundle does not create instances or pass options to them.

2. Custom Options Processor

For modifying the underlying generator's configuration before PDF generation. Useful for adding allowed files, setting paper size, or injecting security options.

Via attribute:

use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfOptionsProcessor;
use Sylius\PdfGenerationBundle\Core\Processor\OptionsProcessorInterface;

#[AsPdfOptionsProcessor(adapter: 'knp_snappy', context: 'invoice', priority: 10)]
final class InvoiceOptionsProcessor implements OptionsProcessorInterface
{
    public function process(object $generator, string $context = 'default'): void
    {
        // $generator is Knp\Snappy\AbstractGenerator for knp_snappy
        $generator->setOption('page-size', 'A4');
    }
}

Via service tag:

App\Pdf\InvoiceOptionsProcessor:
    tags:
        - { name: 'sylius_pdf_generation.options_processor', adapter: 'knp_snappy', context: 'invoice', priority: 10 }

Tag attributes:

  • adapter (required): which adapter type this processor applies to
  • context (optional, default: 'default'): which context; omit to apply to all contexts using this adapter
  • priority (optional, default: 0): higher = runs first

3. Custom Generator Provider

For controlling how the underlying generator object is created or reused per context.

Via attribute:

use Sylius\PdfGenerationBundle\Core\Attribute\AsPdfGeneratorProvider;
use Sylius\PdfGenerationBundle\Core\Provider\GeneratorProviderInterface;

#[AsPdfGeneratorProvider(adapter: 'knp_snappy', context: 'invoice')]
final class InvoiceGeneratorProvider implements GeneratorProviderInterface
{
    public function get(?string $context = null): object
    {
        // Return a custom-configured Knp\Snappy\Pdf instance
    }
}

Via service tag:

App\Pdf\InvoiceGeneratorProvider:
    tags:
        - { name: 'sylius_pdf_generation.generator_provider', adapter: 'knp_snappy', context: 'invoice' }

4. Replacing Core Services

All core services are aliased by interface. Override via Symfony's service decoration:

Sylius\PdfGenerationBundle\Core\Renderer\HtmlToPdfRendererInterface:     # alias -> sylius_pdf_generation.renderer.html
Sylius\PdfGenerationBundle\Core\Renderer\TwigToPdfRendererInterface:     # alias -> sylius_pdf_generation.renderer.twig
Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface:  # alias -> sylius_pdf_generation.manager
Sylius\PdfGenerationBundle\Core\Adapter\PdfGenerationAdapterInterface:   # alias -> sylius_pdf_generation.adapter.default
Sylius\PdfGenerationBundle\Core\Registry\GeneratorProviderRegistryInterface:  # alias -> sylius_pdf_generation.registry.generator_provider

Built-in Adapters

KnpSnappy (knp_snappy)

  • Requires: knplabs/knp-snappy-bundle ^1.10
  • Uses wkhtmltopdf binary under the hood
  • KnpSnappyOptionsProcessor sets options on AbstractGenerator and resolves allowed_files via FileLocator
  • KnpSnappyGeneratorProvider creates a fresh Knp\Snappy\Pdf instance per call via a prototype factory closure (backed by RegisterKnpSnappyPrototypePass)

Dompdf (dompdf)

  • Requires: dompdf/dompdf ^3.1
  • Pure PHP, no external binary
  • DompdfGeneratorProvider creates fresh Dompdf\Dompdf instances

Gotenberg (gotenberg)

  • Requires: gotenberg/gotenberg-php ^2.0
  • Uses Gotenberg's Docker-based API with headless Chromium for HTML-to-PDF conversion
  • GotenbergGeneratorProvider creates a fresh ChromiumPdf builder per call, connecting to the Gotenberg server at the configured base_url
  • Adapter flow: gets ChromiumPdf builder → processes options → calls $builder->html(Stream::string(...)) to build the request → sends via Gotenberg::send() → returns PDF bytes
  • Configure the Gotenberg server URL via sylius_pdf_generation.gotenberg.base_url (defaults to http://localhost:3000)

All adapters are optional dependencies. Configuring an adapter whose package is not installed throws a LogicException with a composer require hint.

Built-in Storage Backends

Flysystem (flysystem)

  • Requires: league/flysystem-bundle ^3.0 (optional)
  • Constructor: FilesystemOperator $filesystem, LocalFilesystem $localFilesystem, string $prefix = '', ?string $localCacheDirectory = null
  • Paths use / separator (Flysystem normalizes), sets storagePath on saved/retrieved files
  • resolveLocalPath() requires local_cache_directory config; lazily fetches and caches locally, invalidates on save/remove

Symfony Filesystem (filesystem)

  • Requires: symfony/filesystem (hard dependency)
  • Constructor: Filesystem $filesystem, string $directory
  • Uses DIRECTORY_SEPARATOR for paths, sets storagePath on saved/retrieved files
  • resolveLocalPath() delegates to internal resolvePath() — file is already local

Gaufrette (gaufrette)

  • Requires: knplabs/knp-gaufrette-bundle (optional)
  • Constructor: GaufretteFilesystem $filesystem, LocalFilesystem $localFilesystem, string $prefix = '', ?string $localCacheDirectory = null
  • Paths use / separator, sets storagePath on saved/retrieved files
  • resolveLocalPath() requires local_cache_directory config; lazily fetches and caches locally, invalidates on save/remove

Development

vendor/bin/phpunit                              # tests
vendor/bin/phpstan analyse -c phpstan.dist.neon  # static analysis
vendor/bin/ecs check                            # coding standards

Tests skip gracefully when optional dependencies are missing via setUpBeforeClass() + markTestSkipped().

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle