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

Unoconv Bundle Laravel Package

jmd/unoconv-bundle

A Symfony bundle that wraps unoconv to convert office documents such as DOCX, ODT, XLSX and PPT into PDF or other formats using LibreOffice. It exposes a simple service for handling conversions in your PHP applications, keeping document processing out of your main codebase.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing first. jmd/unoconv-bundle is a Symfony 2/3 bundle, not a Laravel package. It has no ServiceProvider, no facade, no config publish tag. It was last released in 2016 and is archived, so treat it as a reference implementation for wrapping unoconv — not as a dependency you composer require into a Laravel app and expect to autowire.

Practical first steps:

  1. Read the bundle's source, don't install it. The useful parts are ~2 classes (a converter service that shells out to unoconv) plus a small DependencyInjection/Configuration.php for defaults. Clone it, read Resources/config/services.yml, then reimplement 40 lines in app/Services/.
  2. Provision the host before writing PHP. The conversion happens entirely outside PHP:
    apt-get install -y unoconv libreoffice-writer libreoffice-calc libreoffice-impress \
                       fonts-liberation fonts-dejavu
    unoconv --version && soffice --version
    
    unoconv needs a reachable LibreOffice UNO listener. Either let it spawn one per call (slow, ~2–5s cold start) or run a persistent one:
    unoconv --listener --server 127.0.0.1 --port 2002 &
    
  3. First use case: an uploaded .docx/.xlsx/.pptx → PDF stored in storage/app/converted/{uuid}.pdf, triggered from a controller or (better) a queued job.

If you genuinely are on Symfony legacy, register JMD\UnoconvBundle\JMDUnoconvBundle in AppKernel.php and configure the jmd_unoconv node. On Laravel, skip that entirely and use symfony/process (already a transitive dep of many packages, and Laravel 10+ ships Illuminate\Process).

Minimal Laravel-shaped equivalent:

// app/Services/UnoconvConverter.php
use Illuminate\Support\Facades\Process;

class UnoconvConverter
{
    public function toPdf(string $source, string $outDir): string
    {
        $outDir = rtrim($outDir, '/');
        $result = Process::timeout(120)->run([
            'unoconv',
            '--server', '127.0.0.1', '--port', '2002',
            '-f', 'pdf',
            '-o', $outDir.'/',
            $source,
        ]);

        abort_unless($result->successful(), 500, $result->errorOutput());

        $pdf = $outDir.'/'.pathinfo($source, PATHINFO_FILENAME).'.pdf';
        abort_unless(is_file($pdf) && filesize($pdf) > 0, 500, 'unoconv produced no output');

        return $pdf;
    }
}

Implementation Patterns

Wrap it behind an interface. This is the single highest-value habit here, because you will outgrow unoconv:

interface DocumentConverter {
    public function convert(string $path, string $format): string;
}

// AppServiceProvider
$this->app->bind(DocumentConverter::class, UnoconvConverter::class);

When unoconv inevitably gives you trouble you swap in a Gotenberg HTTP client or a soffice --headless implementation without touching call sites.

Always convert inside a queued job. Conversions are 1–30s of CPU and spawn child processes; they do not belong in a request cycle.

class ConvertDocument implements ShouldQueue
{
    public int $timeout = 180;
    public int $tries = 3;
    public array $backoff = [10, 60];

    public function __construct(public int $documentId) {}

    public function handle(DocumentConverter $converter)
    {
        $doc = Document::findOrFail($this->documentId);
        $out = storage_path("app/converted/{$doc->uuid}.pdf");

        $converter->convert(Storage::path($doc->path), 'pdf');

        $doc->update(['converted_path' => $out, 'status' => 'converted']);
    }

    public function failed(\Throwable $e) { /* mark doc failed, log stderr */ }
}

Persistent listener + per-job profile for concurrency. The listener avoids cold starts, but LibreOffice cannot share a single user profile across simultaneous conversions. Give each worker its own:

'-env:UserInstallation=file:///tmp/lo-profile-'.getmypid()

…or serialize with a dedicated single-worker queue (queue:work --queue=conversions) plus WithoutOverlapping middleware.

Format mapping. -f takes a unoconv filter name, not always an extension: pdf, docx, xlsx, pptx, html, txt, csv. Derive it from the requested target extension and whitelist it — never interpolate user input into the filter or path.

Model the state machine. Add conversion_status (pending|processing|converted|failed) and conversion_error to the documents table. unoconv failures are opaque; persisting stderr is the only sane debugging path.

Validate before you shell out. mimes:doc,docx,xls,xlsx,ppt,pptx,odt,rtf,txt + a max: size. Reject anything not on the whitelist before LibreOffice parses it — the parser is the attack surface.

Cleanup pattern. Convert into a per-job temp dir, then move the artifact into permanent storage. unoconv writes next to the source by default, so always pass explicit -o.

Gotchas and Tips

  • Don't install the package. Archived since 2016, 0 stars, 0 dependents, Symfony 2/3 container YAML. composer require jmd/unoconv-bundle on Laravel 10/11 gets you an unused bundle and a stale lock entry. Copy the idea, not the code.
  • unoconv itself is effectively unmaintained. Python 2 heritage, patchy on Python 3, and behavior shifted across LibreOffice 5→7. Pin your distro's versions in the Docker image and don't apt upgrade blindly.
  • Exit code 0 does not mean success. unoconv sometimes returns 0 with no file written. Always assert is_file() && filesize() > 0, and validate the magic bytes (%PDF) instead of trusting the extension.
  • Zombie soffice.bin processes are the #1 operational failure. When a job times out, the child survives, holds the profile lock, and every subsequent conversion hangs. Add a reaper: kill processes older than N minutes matching soffice for your app user, or run conversions in a short-lived container.
  • $HOME must exist and be writable by www-data. LibreOffice writes to ~/.config/libreoffice; a user with /usr/sbin/nologin and no home dir fails with a cryptic "Failed to connect to socket".
  • Never use exec("unoconv {$file}"). Filenames contain spaces, quotes, unicode. Use Process::run([...]) with array args — argv, not a shell string.
  • Concurrent jobs collide. Symptom: intermittent "Failed to launch / connect to soffice". Fix: per-process -env:UserInstallation, or a single-worker queue. Pick one deliberately.
  • Fonts. Slim containers produce PDFs with substituted glyphs. Install fonts-liberation, fonts-dejavu, plus any brand fonts, and visually diff one output.
  • Security. LibreOffice parses hostile binaries and can execute macros. Run as non-root, no network namespace (--network=none), drop the macro execution path, and cap CPU/memory with cgroups. This is a strong argument for a Gotenberg sidecar over in-process shelling.
  • Debugging recipe: reproduce as the same OS user — sudo -u www-data unoconv -f pdf -o /tmp/out /tmp/in.docx — and log stdout + stderr + exit code from the Process result. unoconv --listener logs to the foreground only; capture it under supervisor.
  • Extension point worth keeping: the bundle's swappable converter service maps cleanly to Laravel's container binding. Keep that seam so Gotenberg (HTTP, containerized, one POST /forms/libreoffice/convert, no shared profiles, no zombies) is a drop-in later.
  • Bottom line for a Laravel shop: if you need DOCX→PDF at any volume, start with Gotenberg in Docker or soffice --headless --convert-to pdf under a queue worker. Reach for this bundle's pattern only as a shape reference for the thin DocumentConverter abstraction.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource