PHPackages                             mkgrow/docx-merge - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. mkgrow/docx-merge

ActiveLibrary

mkgrow/docx-merge
=================

Framework-agnostic DOCX document merging by markers.

v0.0.0-beta(1mo ago)01↑2900%MITPHPPHP &gt;=8.2CI passing

Since Mar 27Pushed 1mo agoCompare

[ Source](https://github.com/MakeGrow/DocxMerge)[ Packagist](https://packagist.org/packages/mkgrow/docx-merge)[ RSS](/packages/mkgrow-docx-merge/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (4)Versions (2)Used By (0)

DocxMerge
=========

[](#docxmerge)

[![CI](https://github.com/mkgrow/docx-merge/actions/workflows/ci.yml/badge.svg)](https://github.com/mkgrow/docx-merge/actions/workflows/ci.yml)[![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP 8.2+](https://camo.githubusercontent.com/9051b6544f57608ca691a918b38ad8c567657e0ecb6d4e960be274f287d171d8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3838393242462e737667)](https://www.php.net/)

A framework-agnostic PHP 8.2+ Composer library that merges DOCX documents by substituting `${MARKER}` placeholders in a template with content extracted from source `.docx` files. Supports multi-section extraction, style deduplication, numbering resequencing, media copying, and header/footer merging -- all while maintaining valid OOXML document structure.

Requirements
------------

[](#requirements)

RequirementVersionPHP&gt;= 8.2ext-zip\*ext-dom\*ext-xml\*ext-mbstring\*psr/log^3.0Installation
------------

[](#installation)

```
composer require mkgrow/docx-merge
```

Quick Start
-----------

[](#quick-start)

```
use DocxMerge\DocxMerger;

$merger = new DocxMerger();

$result = $merger->merge(
    templatePath: '/path/to/template.docx',
    merges: [
        'INTRODUCTION' => '/path/to/intro.docx',
        'CONCLUSION'   => '/path/to/conclusion.docx',
    ],
    outputPath: '/path/to/output.docx',
);

if ($result->success) {
    echo "Merged {$result->stats['markers_replaced']} markers in {$result->executionTime}s\n";
}
```

The template document must contain `${MARKER}` placeholders (e.g., `${INTRODUCTION}`, `${CONCLUSION}`) as paragraph text. Each placeholder is replaced with the full body content of the corresponding source DOCX file.

Usage
-----

[](#usage)

### Simple single-marker merge

[](#simple-single-marker-merge)

```
use DocxMerge\DocxMerger;

$merger = new DocxMerger();

$result = $merger->merge(
    templatePath: '/templates/report.docx',
    merges: [
        'CONTENT' => '/sources/content.docx',
    ],
    outputPath: '/output/report.docx',
);
```

### Multiple markers with different sources

[](#multiple-markers-with-different-sources)

```
$result = $merger->merge(
    templatePath: '/templates/contract.docx',
    merges: [
        'HEADER'      => '/sources/header.docx',
        'TERMS'       => '/sources/terms.docx',
        'APPENDIX_A'  => '/sources/appendix-a.docx',
        'APPENDIX_B'  => '/sources/appendix-b.docx',
    ],
    outputPath: '/output/contract.docx',
);
```

### Section targeting with MergeDefinition

[](#section-targeting-with-mergedefinition)

When a source document has multiple sections, use `MergeDefinition` to extract a specific section by its zero-based index:

```
use DocxMerge\DocxMerger;
use DocxMerge\Dto\MergeDefinition;

$merger = new DocxMerger();

$result = $merger->merge(
    templatePath: '/templates/book.docx',
    merges: [
        'CHAPTER_ONE' => new MergeDefinition(
            markerName: 'CHAPTER_ONE',
            sourcePath: '/sources/chapters.docx',
            sectionIndex: 0,
        ),
        'CHAPTER_TWO' => new MergeDefinition(
            markerName: 'CHAPTER_TWO',
            sourcePath: '/sources/chapters.docx',
            sectionIndex: 1,
        ),
    ],
    outputPath: '/output/book.docx',
);
```

### Configuring MergeOptions

[](#configuring-mergeoptions)

#### Strict marker mode

[](#strict-marker-mode)

Throws `MarkerNotFoundException` when a marker is not found in the template, instead of silently skipping it:

```
use DocxMerge\Dto\MergeOptions;

$options = new MergeOptions(strictMarkers: true);

$result = $merger->merge($template, $merges, $output, $options);
```

#### Custom marker pattern

[](#custom-marker-pattern)

Override the default `${MARKER}` pattern with a custom regex. The first capture group must contain the marker name:

```
use DocxMerge\Dto\MergeOptions;

// Match {{MARKER}} instead of ${MARKER}
$options = new MergeOptions(
    markerPattern: '/\{\{([A-Z_][A-Z0-9_]*)\}\}/',
);
```

#### Reprocessing mode

[](#reprocessing-mode)

Merge additional markers into a previously generated output file:

```
use DocxMerge\Dto\MergeOptions;

// First pass
$merger->merge($template, ['HEADER' => $header], $output);

// Second pass: merge more markers into the same output
$options = new MergeOptions(isReprocessing: true);
$merger->merge($template, ['FOOTER' => $footer], $output, $options);
```

### Handling the MergeResult

[](#handling-the-mergeresult)

`MergeResult` provides structured feedback about the merge operation:

```
$result = $merger->merge($template, $merges, $output);

// Check success
if (!$result->success) {
    foreach ($result->errors as $error) {
        error_log("Merge error: {$error}");
    }
}

// Inspect warnings
foreach ($result->warnings as $warning) {
    echo "Warning: {$warning}\n";
}

// Access processing stats
echo "Markers replaced: {$result->stats['markers_replaced']}\n";
echo "Execution time: {$result->executionTime}s\n";
echo "Output: {$result->outputPath}\n";
```

### Error handling with typed exceptions

[](#error-handling-with-typed-exceptions)

All exceptions extend `DocxMergeException`, allowing both broad and fine-grained error handling:

```
use DocxMerge\DocxMerger;
use DocxMerge\Exception\InvalidTemplateException;
use DocxMerge\Exception\InvalidSourceException;
use DocxMerge\Exception\MarkerNotFoundException;
use DocxMerge\Exception\DocxMergeException;

$merger = new DocxMerger();

try {
    $result = $merger->merge($template, $merges, $output);
} catch (InvalidTemplateException $e) {
    // Template file not found or not a valid DOCX
} catch (InvalidSourceException $e) {
    // Source file invalid or section index out of bounds
} catch (MarkerNotFoundException $e) {
    // Marker not found (only when strictMarkers is true)
} catch (DocxMergeException $e) {
    // Any other library error (XmlParseException, ZipOperationException, MergeException)
}
```

### Using a PSR-3 logger

[](#using-a-psr-3-logger)

Pass any PSR-3 compatible logger to receive diagnostic output:

```
use DocxMerge\DocxMerger;
use Psr\Log\LoggerInterface;

/** @var LoggerInterface $logger Your application's logger */
$merger = new DocxMerger(logger: $logger);

$result = $merger->merge($template, $merges, $output);
```

API Reference
-------------

[](#api-reference)

ClassDescriptionDocumentation`DocxMerger`Public facade with `merge()` method[docs/api/docx-merger.md](docs/api/docx-merger.md)`MergeDefinition`DTO for marker-to-source binding with section targeting[docs/api/merge-definition.md](docs/api/merge-definition.md)`MergeOptions`DTO for merge configuration (pattern, strict mode, reprocessing)[docs/api/merge-options.md](docs/api/merge-options.md)`MergeResult`DTO for merge results (success, stats, errors, warnings)[docs/api/merge-result.md](docs/api/merge-result.md)Exceptions6 typed exceptions extending `DocxMergeException`[docs/api/exceptions.md](docs/api/exceptions.md)Architecture Overview
---------------------

[](#architecture-overview)

DocxMerge follows a 4-layer architecture with strict downward dependency flow:

LayerComponentsResponsibility**1. Public API**`DocxMerger`, DTOsEntry point, input validation, normalization**2. Orchestration**`MergeOrchestrator`, `MergeContext`13-phase pipeline coordination, per-operation state**3. Domain Services**12 services (e.g., `MarkerLocator`, `StyleMerger`, `IdRemapper`)Individual merge concerns (styles, numbering, media, relationships)**4. Infrastructure**`XmlHelper`, `ZipHelper`, `IdTracker`, `SourceDocumentCache`XML/ZIP utilities, ID tracking, document cachingAll domain services depend on interfaces and are stateless (per ADR-001). Per-operation mutable state is encapsulated in `MergeContext`.

For complete architecture documentation, see [docs/system-design/](docs/system-design/).

Development
-----------

[](#development)

### Prerequisites

[](#prerequisites)

- PHP 8.2+ with extensions: zip, dom, xml, mbstring
- Composer 2.x

### Setup

[](#setup)

```
git clone https://github.com/mkgrow/docx-merge.git
cd docx-merge
composer install
```

### Commands

[](#commands)

CommandDescription`composer test`Run the full Pest v3 test suite`composer test --filter=Name`Run a specific describe block or test`composer test:coverage`Run tests with coverage gate (minimum 90%)`composer analyse`Run PHPStan level 8 on `src/` and `tests/``composer format`Apply PSR-12 formatting via php-cs-fixer`composer format:check`Check PSR-12 compliance without modifying files`composer ci`Full quality gate: analyse + format:check + test:coverage### Testing

[](#testing)

The test suite uses Pest v3 with 182 tests and 357 assertions, achieving 95.2% line coverage.

- **Unit tests** (`tests/Unit/`): Pure in-memory XML tests with no filesystem access.
- **Integration tests** (`tests/Integration/`): Real `.docx` fixture-based tests using `tests/Integration/Fixtures/`.

```
# Run all tests
composer test

# Run with coverage
composer test:coverage
```

### Quality Gate

[](#quality-gate)

Before submitting changes, ensure the full CI pipeline passes:

```
composer ci
```

This runs PHPStan level 8, PSR-12 format check, and test coverage gate (minimum 90%) in sequence.

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md) for a detailed version history.

License
-------

[](#license)

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 93.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/46572613?v=4)[Mateus Bandeira](/maintainers/mateusbandeira182)[@mateusbandeira182](https://github.com/mateusbandeira182)

---

Top Contributors

[![mateusbandeira182](https://avatars.githubusercontent.com/u/46572613?v=4)](https://github.com/mateusbandeira182 "mateusbandeira182 (28 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (2 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mkgrow-docx-merge/health.svg)

```
[![Health](https://phpackages.com/badges/mkgrow-docx-merge/health.svg)](https://phpackages.com/packages/mkgrow-docx-merge)
```

###  Alternatives

[elgg/elgg

Elgg is an award-winning social networking engine, delivering the building blocks that enable businesses, schools, universities and associations to create their own fully-featured social networks and applications.

1.7k15.7k5](/packages/elgg-elgg)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[api-platform/metadata

API Resource-oriented metadata attributes and factories

223.5M96](/packages/api-platform-metadata)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[flowwow/cloudpayments-php-client

cloudpayments api client

2188.2k](/packages/flowwow-cloudpayments-php-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
