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

Apache Fop Laravel Package

goetas/apache-fop

PHP wrapper for Apache FOP that renders XSL-FO documents into PDF and other output formats. It offers a simple API to run FOP transformations and integrate FO-based PDF generation into Laravel or any PHP application.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a thin Symfony wrapper around the Apache FOP command-line tool: you give it XSL-FO (either a file or a string), it shells out to fop/java -jar fop.jar, and you get a PDF back. Before writing any PHP, confirm the hard prerequisite is in place — a JVM and a working FOP install:

java -version
fop -version   # or: java -jar /opt/fop/fop.jar -version

Then install and register the bundle. Note it targets Symfony 2/3 (last release May 2017), so on Symfony 4+ you will register it manually in config/bundles.php rather than AppKernel:

composer require goetas/apache-fop
// config/bundles.php
Goetas\ApacheFopBundle\ApacheFopBundle::class => ['all' => true],

Minimal configuration (check the bundle's DependencyInjection/Configuration.php for the exact key names in your version — it wires the FOP executable path, the Java binary, and a temp directory):

# config/packages/apache_fop.yaml
apache_fop:
    fop_path: '%kernel.project_dir%/bin/fop'
    # java_path: /usr/bin/java
    # temp_dir: '%kernel.cache_dir%/fop'

First use case — render a hand-written .fo file to PDF from a controller or console command:

use Goetas\ApacheFopBundle\Fop\Fop;

public function render(Fop $fop): Response
{
    $fop->generatePdf(
        $this->getParameter('kernel.project_dir').'/var/invoice.fo',
        '/tmp/invoice.pdf'
    );

    return $this->file('/tmp/invoice.pdf');
}

Where to look first: the bundle's Fop service class (it is small and shows exactly which process arguments are built), Resources/config/services.xml for the service ID to inject, and the Tests/ directory for a fixture .fo file you can render immediately to prove the toolchain works end-to-end.

Implementation Patterns

XSLT → FO → PDF pipeline. The bundle does not transform your data into XSL-FO; that is your job. The standard workflow is: serialize your domain object to XML (Symfony Serializer or Twig-rendered XML), run it through an XSLT stylesheet (XSLTProcessor in ext-xsl), then hand the resulting .fo to FOP. Keep the three steps as separate services so each is testable without a JVM.

$xml  = $this->serializer->serialize($invoice, 'xml');
$xsl  = new \XSLTProcessor();
$xsl->importStylesheet(simplexml_load_file($template.'.xsl'));
$fo   = $xsl->transformToXml(simplexml_load_string($xml));

$fop->generatePdf($this->writeTemp($fo), $pdfPath);

Stream vs. file. For anything beyond toy documents, generate to a temp file, then either return $this->file(...) or copy it into your storage layer. Streaming FOP's stdout directly is possible but makes error reporting much worse (exit code and stderr get tangled with binary output).

Cache aggressively. FOP is slow (JVM startup alone is 0.5–2s). Key a cache entry on hash(xml + xsl + config) and store the PDF in var/cache or your object store. In practice this turns a 2s request into a 5ms one and is the single biggest day-to-day win.

Move generation off the request thread. Wire a console command that consumes a queue (or a simple doctrine jobs table on old Symfony versions) and have controllers enqueue a job. Return a download link once the PDF exists.

Wrap it if you need control. The service is thin; decorating it to add timing, logging, and retry logic is cleaner than editing vendor code:

services:
    App\Fop\LoggingFop:
        decorates: 'goetas_apache_fop'
        arguments: ['@App\Fop\LoggingFop.inner', '@logger']

Testing. In unit tests, mock/replace the Fop service so no JVM is needed. Keep one slow integration test (marked @group integration) that renders a known .fo file and asserts the output starts with %PDF-.

Gotchas and Tips

  • The bundle adds little over Process. It is essentially argument-building plus config. If it fights your Symfony version, dropping it and calling new Process(['fop', '-fo', $in, '-pdf', $out]) yourself is a legitimate, low-risk move — you lose nothing but a YAML block.
  • Maintenance risk is real. Last release 2017, 8 stars, zero known dependents. Budget for a fork or an in-house wrapper. Do not build a critical document pipeline on it without pinning the version and writing integration tests around the FOP binary itself.
  • Symfony 4+ autowiring will not find the service. Inject by service ID ($fop = $container->get('goetas_apache_fop')) or add an alias to Fop::class in your own services.yaml. The bundle's XML service definitions predate autoconfigure.
  • Fonts are the #1 "it works locally" failure. FOP resolves fonts via its own fop.xconf, not your system font cache. Ship an explicit fop.xconf (with <fonts> pointing at bundled TTFs) and pass it via -c. Otherwise production renders boxes/tofu where dev rendered text.
  • Temp directory and concurrency. Concurrent FOP runs need unique input/output paths; never hardcode one filename. Ensure temp_dir is writable by the web user and is not a tmpfs so small it fills during large renders.
  • The JVM is the failure domain, not the bundle. Set -Xmx via JAVA_OPTS (or FOP's -X style options if supported by your wrapper) for big documents; set a Process timeout, because a malformed FO can hang FOP indefinitely.
  • Debug by reproducing the exact command. Log the fully-qualified command line the service builds, then run it by hand in the same shell user as PHP-FPM. FOP's stderr messages (e.g. "no protocol", "unknown formatting object") are far more informative than the wrapper's exception.
  • Validate FO early. FOP errors surface late and cryptically; a quick schema validation of the .fo in dev catches unclosed blocks before they reach the JVM.
  • Extension points are minimal — there is no event or formatter hook. Real customization happens in the .xconf files (fonts, hyphenation, image resolution) and in your XSLT, both of which are plain files you control outside the bundle.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource