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

mbence/opentbs-bundle

A Symfony bundle that integrates OpenTBS, the PHP template engine for merging data into OpenOffice and Microsoft Office documents such as docx, xlsx, pptx, and odt files. Generate dynamic reports and documents from templates using simple tags.

View on GitHub
Deep Wiki
Context7

Getting Started

Don't install the bundle — take the library. mbence/opentbs-bundle is a Symfony bundle (DI extension, services.xml, kernel wiring) last released in 2016 and archived. Its composer.json targets Symfony 2/3 and PHP 5.x, so composer require mbence/opentbs-bundle in a Laravel 9+ app will either hard-conflict or drag in an ancient Symfony polyfill stack. The bundle contains almost no logic of its own — it is a thin container wrapper around TinyButStrong's clsTinyButStrong + clsOpenTBS plugin. Pull that instead:

composer require tinybutstrong/tinybutstrong-opentbs   # or vendor clsOpenTBS.php + tbs_class.php directly

Where to look first in the repo: Resources/config/services.xml (shows exactly what the bundle instantiates — a clsTinyButStrong with TBS_INSTALL, OPENTBS_PLUGIN applied), the Service/ class that loads templates, and any demo .docx/.odt files under Resources/. Those demos are the most reusable artifact in the package; the PHP glue you can rewrite in 20 lines.

First use case: a certificate / appointment letter / quote. Author storage/app/templates/quote.docx in Word with [client.name], [quote.reference], [quote.total] typed in one go, then:

$tbs = new clsTinyButStrong;
$tbs->Plugin(TBS_INSTALL, OPENTBS_PLUGIN);
$tbs->LoadTemplate(storage_path('app/templates/quote.docx'), OPENTBS_ALREADY_UTF8);
$tbs->MergeField('client', $client->toArray());
$tbs->MergeField('quote',  $quote->toArray());
return response()->streamDownload(fn () => $tbs->Show(OPENTBS_STRING, 'quote.docx'), 'quote.docx');

That is the whole package, minus the Symfony ceremony.

Implementation Patterns

Bind a fresh instance per render. OpenTBS keeps merge state on the instance; never register it as a shared singleton (this bites hard under Octane/queue workers). A DocumentGenerator action class that constructs a new clsTinyButStrong per call is the right shape:

final class DocumentGenerator
{
    public function render(string $templatePath, array $data, string $outName): StreamedResponse
    {
        $tbs = app(OpenTbsFactory::class)->make();   // new instance each time
        $tbs->LoadTemplate($templatePath, OPENTBS_ALREADY_UTF8);
        foreach ($data as $key => $value) {
            is_array($value) && array_is_list($value)
                ? $tbs->MergeBlock($key, $value)
                : $tbs->MergeField($key, $value);
        }
        return response()->streamDownload(fn () => $tbs->Show(OPENTBS_STRING, $outName), $outName);
    }
}

Merge patterns you'll actually use:

Need Call
Scalars / nested arrays MergeField('quote', [...]) → [quote.total]
Repeating table rows MergeBlock('item', $rows) → [item.name;block=tr] on the row
Sub-blocks / conditionals [item.notes;block=tr], [onshow.item.notes;when [item.notes]!='']
Replace a logo MergeField('logo', storage_path(...)) → [logo;ope=changepic]
Date/number formatting [quote.date;ope=date(Y-m-d)], [quote.total;ope=numformat]
Multi-cell layout tricks [subtotal;ope=mergecell], [x;ope=clearcell]

Registry, not hardcoded paths. Store template filename + required keys in a document_templates table or config map so marketing can drop a new .docx in a disk without a deploy; version templates by filename (quote-v3.docx) and keep the placeholder contract in a README next to them.

Output routing. OPENTBS_STRING → response()->streamDownload(); OPENTBS_FILE → Storage::disk('local')->path() then Mail::attach(); OPENTBS_DOWNLOAD is a bad fit for Laravel (it echoes headers itself and fights the response stack).

Testing. Golden-file tests: render deterministic data with OPENTBS_STRING, unzip the result in the test (ZipArchive), and assert word/document.xml contains the expected strings. That catches broken placeholders that a byte-hash comparison would flag on every Word save.

Gotchas and Tips

  • Abandoned upstream. No fixes for PHP 8. Older TBS (each(), {} string offsets, dynamic properties) throws deprecations or fatals; pin TBS 3.15+/OpenTBS 1.10+ or stay on PHP 7.4. Since the bundle is archived with 0 dependents, you own the code — vendor it and vendor the OpenTBS class rather than trusting Packagist resolution.
  • Temp directory. OpenTBS extracts the OOXML zip to a temp dir. In read-only containers (Docker, Lambda, Forge-deployed hardened images) set an explicit writable path (TBS_TEMP_DIR / sys_temp_dir), or you get "cannot open file" / "not a zip archive" deep inside TbsZip.
  • Word splits your placeholders. The classic failure: typing [client.name] with autocorrect, then editing mid-string leaves it fragmented across <w:r> runs, and TBS silently leaves the tag in the output. Paste as plain text, type in one pass, and never let Word "help" with brackets. Stray [ anywhere in body text is treated as a field.
  • Block syntax is positional. block=tr must sit on the tag in the row that repeats; nested blocks need the inner block declared first and named distinctly. A block that repeats the header row is the #1 "why is my table wrong" bug.
  • Line breaks don't survive. \n in a merged value renders as a space in Word. Use separate block rows/paragraphs, or split on \n and merge an array.
  • XML escaping is handled, HTML is not. OpenTBS escapes XML for you (with OPENTBS_ALREADY_UTF8), so pass entities as literals — but you cannot inject rich text/HTML. Anything beyond plain text needs template-side design.
  • Computed fields don't recalculate server-side. Excel formulas, NUMPAGES, TOC entries, and cross-references stay stale until opened in Word/Excel. For XLSX, either write values instead of formulas or run LibreOffice headless if the output must be final.
  • Debug via XML dump. Use Show(OPENTBS_DEBUG_XML_SHOW) (or OPENTBS_DEBUG_XML for the file) to get the merged document.xml as text instead of a document — infinitely faster than opening Word to see whether [quote.total] merged. The bundle modeled this as a debug config flag; in Laravel just gate it on app()->isLocal().
  • Extension point to copy from the bundle: the Symfony version lets you override the opentbs service. In Laravel, bind an OpenTbsFactory interface in a service provider and swap in a subclass (for temp dir, charset, or a custom ope= magnet) via $this->app->bind().
  • Licensing: TinyButStrong/OpenTBS is LGPL — fine to use, but keep the library as a separate, replaceable dependency (exactly what Composer gives you) if you ship closed source.
  • When to skip it: for simple placeholder .docx mail-merge, phpoffice/phpword's TemplateProcessor is the more mainstream Laravel choice and has a live maintainer. OpenTBS still wins on ODT, XLSX, dynamic image swapping (changepic), and complex repeating/nested table layouts.
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.
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen