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

Opentbs Bundle Laravel Package

jonasarts/opentbs-bundle

Symfony bundle integrating OpenTBS for generating Office documents such as DOCX, XLSX, PPTX, ODT, ODS, and ODP from templates. Merge data into templates using TinyButStrong-style tags for dynamic reports, invoices, and exports.

View on GitHub
Deep Wiki
Context7

Getting Started

Pick the engine, not the bundle. jonasarts/opentbs-bundle is a Symfony DI wrapper — a bundle class, a Configuration.php, autowiring aliases, and no business logic. Laravel's container will not load any of it. The reusable payload is OpenTBS (a plugin of TinyButStrong). For a Laravel app, install the engine directly and lose nothing:

composer require tinybutstrong/opentbs

You only reach for the bundle itself if you are genuinely inside Symfony, and even then verify its composer.json symfony/* constraints — with 0 stars, 0 dependents, and a single maintainer, compatibility bumps are not coming on their own.

Confirm prerequisites before writing code. You need ext-zip. OpenTBS is a plain PHP single file: no headless LibreOffice, no COM, no Java sidecar. That is the entire reason it fits a container-based Laravel stack. Run a PHP 8.2/8.3 compatibility spike against real templates first — older OpenTBS releases carry dynamic-property and string-offset deprecations.

Register a scoped instance. TBS is stateful. Do not make it a long-lived singleton across requests (queue workers, Octane). Bind it as a scoped service and always set an explicit cache dir:

// app/Providers/AppServiceProvider.php
use TinyButStrong\clsOpenTBS; // or \clsOpenTBS depending on the pinned version

$this->app->scoped(\clsOpenTBS::class, function () {
    $tbs = new \clsOpenTBS();
    // Where TBS unpacks/rezips the OOXML container. Do not trust defaults
    // in a container with a read-only filesystem.
    $tbs->SetOption('cache_dir', storage_path('app/opentbs-tmp'));
    return $tbs;
});

Create that directory with the right permissions in your image build, and give each render its own subdirectory if you parallelize.

First use case: a single-field contract merge. Author a .docx in Word, insert [client.name] and [contract.total] where the fields go, save it to resources/templates/contract.docx. Then:

$tbs = app(\clsOpenTBS::class);
$tbs->LoadTemplate(resource_path('templates/contract.docx'));
$tbs->MergeField('client', ['name' => 'Acme Ltd']);
$tbs->MergeField('contract', ['total' => '$12,000']);
$tbs->Show(OPENTBS_STRING);
return $tbs->Source; // the .docx bytes

That is the whole loop. Everything else (loops, images, conditionals) is OpenTBS syntax added to the template, not Laravel code. Wrap this in a DocumentRenderer service with render(string $templatePath, array $data): string and you have a reusable seam.

Implementation Patterns

Keep the wrapper thin. The bundle exists to prove the pattern, not to be adopted. Your Laravel wrapper is ~50 lines: a scoped binding, a DocumentRenderer contract, a TemplateRepository that resolves versioned templates (from resources/templates in dev, object storage in production), and an output assertion layer. Everything else is template authoring.

Split data from layout in the template, not the code. The pattern that pays off day-to-day:

  • Scalars: [var.name] for single values — recipient fields, totals, dates.
  • Loops: [b.line_items]…[b.line_items] for row/cell repetition — invoice line items, employee lists, transaction feeds. Excel rows and Word tables both use the same block syntax.
  • Conditionals: [b.clause?jurisdiction=EU]…[b.clause] to include or drop a section. This is what replaces if statements in code.
  • Images: [var.logo;ope=addpic] swaps a picture in place. Signatures, headers, logos.
  • HTML fragments: merge a rendered Blade string into a [var.body;ope=html] field when a section is naturally built as markup.

Non-engineers edit the .docx/.xlsx and never touch the code. That is the productivity win — treat it as the primary workflow, not a fallback.

Batch generation goes through queues, one job per document. Memory scales with template size + embedded images + loop cardinality. A 10k-row XLSX loop with images will hit memory_limit before it hits CPU. Cap loop size at the application layer, set an explicit memory_limit on the queue, and mark documents ready only after assertions pass:

final class GenerateDocumentJob implements ShouldQueue
{
    public function handle(DocumentRenderer $renderer): void
    {
        $bytes = $renderer->render($this->templatePath, $this->data);

        // Assert before persisting — silent failures are the dangerous ones.
        $this->assertNoUnresolvedTags($bytes);
        $this->assertValidZip($bytes);
        $this->assertXmlWellFormed($bytes);

        Storage::disk('documents')->put($this->targetPath, $bytes);
    }
}

Write-then-rename for atomicity; make the job idempotent so retries are safe.

Add a template_version column. When a generated contract is later disputed, you need to know which template revision produced it. Store it alongside the file. Combine with a versioned TemplateRepository and you get auditability essentially for free.

Keep a golden-file test suite. Fixture template + fixture data → assert the ZIP opens, XML is well-formed, and no [...] tags remain. This is what makes depending on an unpinned upstream tolerable. Run it on every template edit as well as every engine bump.

Migrate template-by-template, not wholesale. PhpWord/PhpSpreadsheet are still the right tool for fully programmatic, data-dense output with no designed layout. OpenTBS wins where a designed template exists and a human will re-author it. Do not rewrite working code for its own sake.

Gotchas and Tips

Template trust boundary is the security decision. TBS supports onload handlers and parameter modifiers that reach into PHP and filesystem operations. Never render user-uploaded templates. Templates must be code-reviewed artifacts in version control. Enforce a hard allowlist of template sources in TemplateRepository — this is the single biggest control in the whole integration. Combine with ZIP/XML hygiene: attacker-influenced templates or data invite zip-bomb and entity-expansion concerns.

Silent failure is the failure mode to design against. The expensive bugs are:

  • Unreplaced [client.name] tags shipped to a customer. Scan the output for [ and fail loudly.
  • Stale Excel cached values. OpenTBS manipulates XML but does not recalculate formulas — outputs carry whatever Word/Excel cached at save time. Set fullCalcOnLoad on the workbook, or document clearly that recipients must open in Excel to see updated numbers.
  • Corrupted ZIP that Word "repairs" on open. Validate ZIP integrity before marking the document ready.
  • Partial writes on timeout. Atomic write-then-rename; idempotent retries.

Loud failures — missing template, bad data key, memory exhaustion — are cheap by comparison. Spend the effort on the silent ones.

Known rough edges. Pivot tables, charts, some conditional formats in XLSX, and strict/content-control-heavy DOCX render imperfectly. Verify your representative templates end-to-end before committing. If a feature breaks, you may need to post-process the file or restructure the template — plan the spike to catch this.

Excel recalculation is a real product decision. If the recipient expects to see calculated totals without opening the file, OpenTBS alone will not deliver. Options: compute totals in PHP and merge them as literals (usually the right answer), set fullCalcOnLoad, or hand off to LibreOffice for a recalc pass. Decide early.

Temp directory in containers. Default temp paths are a coin flip on a read-only filesystem. Set cache_dir explicitly, create it at build time, and give concurrent renders distinct subdirectories. A stale cache_dir is also a classic source of "works on my machine" bugs.

Scoped, not singleton. A singleton clsOpenTBS bound at boot will leak state across Octane requests and queue jobs. Use $this->app->scoped(...). Even inside a single job that renders multiple documents, reset or re-instantiate between files rather than reusing a dirty instance.

PDF is out of scope. OpenTBS does not do PDF. If the deliverable must be a PDF, plan a separate step (LibreOffice, a SaaS converter, or a sibling pipeline). Do not let "OpenTBS handles documents" blur into "OpenTBS handles PDF" in stakeholder conversations.

Template documentation is mandatory, not optional. With 0 stars, 0 dependents, and no Stack Overflow corpus, there is no community to lean on. Write down your supported TBS tag subset, the modifiers you allow, and the review process. The team answering "why is this field blank" two years from now will thank you.

Licensing needs a legal eye. The bundle is MIT, but TinyButStrong/OpenTBS is LGPL-2.1. Fine for most SaaS deployments; confirm your distribution model (especially if you ship an on-prem binary) before committing.

Pin the upstream exactly. A 0-star wrapper around a small-team engine is not a dependency you float. Pin tinybutstrong/opentbs to an exact version, keep the golden-file suite green, and budget for occasional manual patches or a fork if upstream stalls.

Extension points you actually have. Because you own the wrapper, everything is extensible: swap the TemplateRepository backend, add output assertions, wrap the engine in a DocumentRenderer contract that a mock can satisfy in tests, and drop in a PhpWord implementation behind the same interface for the handful of documents that genuinely need programmatic construction. That interface is the deliverable — not 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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin