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

Doxswap Laravel Package

blaspsoft/doxswap

DoxSwap is a Laravel package for working with DOCX templates: fill placeholders with data, generate Word documents, and convert them to PDF. It offers a simple, fluent API for building dynamic documents in PHP applications.

Deep Wiki
Context7

Getting Started

DoxSwap is best treated as a document rendering layer, not a full document management system. You keep .docx files as versioned templates in your app, pass a data array into the package, and receive a filled document that you can store, download, or attach to mail.

Minimal setup:

  1. Install the package:
    composer require blaspsoft/doxswap
    
  2. If the package ships a config file, publish it and check the template/output paths:
    php artisan vendor:publish --tag=doxswap-config
    
  3. Open the package's service provider and facade first. The public API surface is small; confirm the exact method names and placeholder syntax before building abstractions around it.
  4. Put templates somewhere predictable, for example:
    resources/templates/offers/offer-v1.docx
    storage/app/templates/contracts/contract-v2.docx
    
  5. Create a template with simple textual placeholders, e.g. {{ customer_name }}, {{ offer_total }}, {{ valid_until }}. Keep the first template deliberately plain: no images, loops, tables, or conditional blocks.
  6. Generate from a controller, artisan command, or queued job and return the file as a download.

A good first use case is an offer letter or invoice-style document: one template, one record, half a dozen scalar placeholders. That validates installation, placeholder syntax, storage paths, and download flow before you attempt tables or repeating sections.

A useful day-to-day structure is to wrap the package in your own small service:

namespace App\Services;

use App\Models\Offer;
use Illuminate\Support\Facades\Storage;

class OfferDocumentService
{
    public function generate(Offer $offer): string
    {
        $template = Storage::disk('templates')->path('offers/offer-v1.docx');
        $output = "offers/{$offer->id}/offer-{$offer->id}.docx";

        // Replace with the package's actual facade/service calls.
        app('doxswap')
            ->template($template)
            ->with($this->dataFor($offer))
            ->save(Storage::disk('documents')->path($output));

        return $output;
    }

    private function dataFor(Offer $offer): array
    {
        return [
            'customer_name' => $offer->customer->name,
            'offer_number' => $offer->reference,
            'offer_total' => number_format($offer->total, 2),
            'valid_until' => $offer->valid_until->format('d M Y'),
        ];
    }
}

This adapter gives you one place to change if the package API or placeholder syntax changes, and it keeps controllers free of document logic.

Implementation Patterns

Use a template registry per document type. Avoid scattering template paths through controllers. Map a document type to a versioned template and keep that mapping in config or a small enum:

enum DocumentTemplate: string
{
    case Offer = 'offers/offer-v1.docx';
    case Contract = 'contracts/contract-v2.docx';
    case CompletionCertificate = 'certificates/completion-v1.docx';
}

When wording changes, add offer-v2.docx and update the mapping. Never overwrite a live template in place; you lose the ability to reproduce old documents.

Normalize data before rendering. The package should receive scalar, display-ready values. Do formatting in PHP, not in the DOCX template:

private function dataFor(Contract $contract): array
{
    return [
        'client_name' => $contract->client->name,
        'client_address' => $contract->client->address,
        'start_date' => $contract->starts_at->format('d/m/Y'),
        'end_date' => $contract->ends_at->format('d/m/Y'),
        'fee' => '€' . number_format($contract->fee_cents / 100, 2),
        'payment_terms' => $contract->payment_terms,
    ];
}

This avoids locale surprises, keeps templates editable by non-developers, and makes output testable.

Use queued jobs for anything nontrivial. DOCX processing is CPU and memory bound. For bulk generation, queued generation, or large templates, dispatch a job:

GenerateOfferDocument::dispatch($offer)->onQueue('documents');

Store the generated file on a private disk, record the path and template version on the model, then notify the user when ready. The request cycle should not wait for document rendering.

Return downloads through a controller with authorization. Never expose storage paths directly. Use a signed URL or an authenticated download route with a policy check:

public function download(Offer $offer)
{
    $this->authorize('downloadDocument', $offer);

    return Storage::disk('documents')->download($offer->document_path);
}

Keep generated files immutable and name them deterministically. Include the record ID, template version, and timestamp:

offers/2025/offer-1842-v1-20251005-143201.docx

This simplifies auditing, cleanup, and support requests.

Test with fixture templates and assertions on extracted text. Keep a tiny tests/Fixtures/templates/simple.docx and assert that the rendered document contains expected values and does not contain unresolved placeholders. If the package does not provide text extraction, unzip the generated .docx and inspect word/document.xml in the test.

Use events and observers for lifecycle integration. Fire a DocumentGenerated event after rendering, then attach listeners for mail delivery, activity logging, or pushing the file to S3. This keeps generation separate from delivery.

Add a preview path for internal users. Generate a watermarked or clearly labeled draft by using a draft template variant (e.g. offer-draft-v1.docx) and the same data mapping. This is useful for operations teams who need to review wording before sending.

Gotchas and Tips

  • DOCX placeholders can be split across XML runs. Word often breaks {{ customer_name }} into multiple <w:r> elements when formatting, spell-check, or editing occurs. The result is a placeholder the package cannot find. Fix by typing placeholders in one pass, ideally pasting them as plain text, and avoid applying character formatting inside the braces. If replacement silently fails, unzip the file and inspect word/document.xml first.
  • Start with scalar placeholders only. Loops over table rows, image replacement, conditional sections, and headers/footers often require package-specific syntax or extra configuration. Confirm support in the package source before promising these features to stakeholders.
  • Special characters matter. Ampersands, angle brackets, and quotes must be valid in the generated XML. If users supply &, <, or >, verify that the package escapes values correctly. A value containing & that causes a corrupt document is a common integration failure.
  • Missing placeholders may fail silently or remain visible. Decide which behavior you need. Add a post-generation check for unresolved placeholder patterns in the output and fail loudly in tests or in a queued job.
  • Template files are ZIP archives. For debugging, copy the generated .docx, rename it to .zip, extract it, and inspect word/document.xml. This is faster than guessing why a replacement did not appear.
  • Be strict about uploads. If users can upload templates, treat DOCX files as untrusted ZIP/XML input. Validate MIME type and extension, limit file size, store outside the web root, and scan or sandbox processing. Do not let arbitrary uploaded documents reach production workflows without review.
  • Watch memory and execution limits. PHPWord-style processing and ZIP handling can be memory hungry for large documents. Set queue timeouts and memory limits deliberately, and test with the largest realistic template rather than a one-page sample.
  • Check path handling on Windows and Linux. Backslashes, temporary directories, file locks, and permissions differ. Prefer Laravel's Storage abstraction and absolute paths from Storage::path() rather than string concatenation.
  • Pin the package version in composer.json. With an evolving document package, a minor release can change placeholder parsing or output behavior. Run your fixture-based tests in CI after every update.
  • Configuration quirks are likely around disks and temp paths. If generation works locally but fails in production, check the configured output disk, temp directory permissions, and whether workers run as a different user than the web process.
  • Extension points to look for: custom placeholder delimiters, value formatters, template loaders, and output naming. If the package exposes them, use them instead of pre-processing templates with string replacement. If it does not, keep your own adapter layer so you can swap implementations later.
  • Do not use DOCX generation as your source of truth. Store structured data in the database and treat the document as a rendered artifact. This lets you regenerate contracts when templates change and avoids parsing generated Word files to recover business data.

Day-to-day, the winning pattern is simple: versioned templates, a small data-mapping service per document type, queued rendering, private storage, authorized downloads, and fixture tests that catch placeholder breakage before users do.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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