ezsystems/ezodf-ls
PHP library for creating and manipulating OpenDocument Format files, including text documents and spreadsheets. Extracted from eZ Publish, it provides classes for generating ODF content programmatically, useful for exports and reports.
This is a legacy eZ Systems library pulled from the eZ Components ODF component. It is not a Laravel package — no service provider, no facade, no config file. You install it and drive the classes directly, so the first job is wrapping it yourself.
composer require ezsystems/ezodf
Check composer.json and the src/ tree before anything else. The entry points are the legacy ezc-prefixed document classes (ezcDocumentOdt, ezcDocumentOds, ezcDocumentOdp), and the primary input format is DocBook XML, not HTML or a fluent builder. That single fact drives most of the design decisions you'll make with this package.
Register a binding in a service provider so you can inject a single wrapper rather than newing classes up across controllers:
// app/Providers/AppServiceProvider.php
$this->app->singleton(OdfExporter::class, fn () => new OdfExporter(
storage_path('app/templates')
));
First use case — the one that maps cleanly onto day-to-day Laravel work — is document export: take structured data (an order, an invoice, a content record), render it to an ODT/ODS, and return it as a download or push it to storage.
Minimal flow:
$doc = new \ezcDocumentOdt();
$doc->loadString($docbookXml);
$doc->save(storage_path('app/exports/order-'.$order->id.'.odt'));
The output is a zip container (ODF is zipped XML), so ext-zip, ext-dom, and likely ext-xsl must be available in your runtime image. Verify those in Dockerfile/php.ini before you start debugging formatting — missing extensions produce confusing failures rather than clean errors.
Wrap, never scatter. Create App\Support\Odf\OdfExporter with methods like toOdt(string $docbook): string and toOds(array $rows): string. Every call into ezc* lives behind that boundary. When the library eventually needs patching (it will), you touch one file.
DocBook as the intermediate layer. Do not try to hand-build ODF XML. Instead, render a Blade view that produces DocBook:
public function toOdt(string $docbook): string
{
$document = new \ezcDocumentOdt();
$document->loadString($docbook);
return $document->saveToString(); // or write to a temp path
}
// In a controller / job:
$docbook = view('exports.invoice-docbook', ['invoice' => $invoice])->render();
$path = $this->exporter->toOdt($docbook);
This keeps your templates in resources/views/exports/ where you can iterate on them and diff them in review, exactly like an HTML mail template.
Template-driven documents. For documents that must match a corporate layout, keep .odt templates on disk, load a template, then substitute. This is the pattern eZ Publish itself used for CMS content export — treat the shipped template as the style source of truth, and only inject content.
Queue the heavy work. ODF assembly is CPU- and memory-bound on large documents and is a poor fit for the request lifecycle. Dispatch an ExportOrderDocument job, write the .odt to the local or s3 disk, and notify the user:
Storage::disk('local')->put("exports/{$order->id}.odt", $bytes);
Because ODF is just a directory tree zipped, S3 storage works fine — no partial-write weirdness as long as you put() the full string rather than streaming the save() call.
Stream downloads explicitly. Write to a temp path, then return response()->download($path, 'invoice.odt', ['Content-Type' => 'application/vnd.oasis.opendocument.text']). The library's in-memory output is a string; a StreamedResponse that calls save('php://output') will fight with Laravel's buffering on some FPM setups.
Spreadsheets for reporting. ezcDocumentOds is the right tool for "export this table to LibreOffice". For anything complex, prefer a plain CSV export and reserve ODS for documents where formatting, multi-sheet structure, or formulas are the actual requirement.
Test against fixtures. Keep a known-good .odt in tests/Fixtures/, generate output, unzip both, and assert on content.xml. Snapshot the unzipped XML rather than the binary — zips contain timestamps and the binary diff is useless.
Extension point: the library's styler/rendering layer is where you reach when default mappings don't suit you. If you find yourself subclassing to adjust DocBook element mapping, do it inside your wrapper and keep the subclass in app/Support/Odf/.
each(), and curly-brace string offsets on modern runtimes. Test on your actual runtime early; do not assume it installs cleanly.NOASSERTION. eZ Components historically shipped under GPL-family terms. Before this goes near a commercial product, get a human to confirm the license file in the repo. Do not infer it from the package name.unzip -o doc.odt -d /tmp/doc && xmllint --format /tmp/doc/content.xml. Almost every "why does it look wrong" question is answered in content.xml or styles.xml.grep -r "class ezcDocumentOdt" vendor/) rather than trusting docs or older tutorials.memory_limit. Enforce a size cap in the job and chunk at the domain level instead of fighting the library.loadString on user-influenced input. If DocBook is assembled from user data, escape values on the Blade side — the library will happily emit malformed XML and fail with an unhelpful parse error.application/vnd.oasis.opendocument.text for .odt, ...spreadsheet for .ods, ...presentation for .odp. Getting this wrong makes browsers download without the right preview behaviour in LibreOffice-adjacent tooling..docx or PDF outputs, PhpWord or a headless LibreOffice conversion step is a better use of time — the maintenance cost of this package is only worth paying when ODF is a hard requirement.How can I help you explore Laravel packages today?