PHPackages                             jwadhams/merge-a-trois - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. jwadhams/merge-a-trois

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

jwadhams/merge-a-trois
======================

Three-way merge for PHP array data

2.0.0(3y ago)534.2k—4.4%1[1 issues](https://github.com/jwadhams/merge-a-trois/issues)MITPHPPHP &gt;=8.1

Since Jul 26Pushed 3y ago1 watchersCompare

[ Source](https://github.com/jwadhams/merge-a-trois)[ Packagist](https://packagist.org/packages/jwadhams/merge-a-trois)[ RSS](/packages/jwadhams-merge-a-trois/feed)WikiDiscussions master Synced yesterday

READMEChangelogDependencies (1)Versions (11)Used By (0)

Merge à Trois - Three-way merge for PHP array data
==================================================

[](#merge-à-trois---three-way-merge-for-php-array-data)

I have a PHP-backended application that serves up a large JSON-encoded unstructured blob of data to JavaScript clients. The clients manipulate bits inside that big object, then return the whole altered object to the back end, possibly minutes later.

It's possible that two people can be collaborating on the same big object roughly-synchronously (typically on different bits inside the large object), so the backend needs a way to merge those changes.

The goal of this library is to silently merge changes and end up with one authoritative server-stored object that as much as possible honors *all* collaborators. Unlike software version control merges, no user is in a place to arbitrate conflicts, so the algorithm *always* returns an answer, and we keep audit trails outside of this library.

When in doubt, the most recent change wins.

Why three-way merge?
--------------------

[](#why-three-way-merge)

Each client is first pulling a known-good version of the object. The three-way merge technique lets us use a common ancestor to the potentially conflicting changes, and is especially useful in noticing deleted content.

Examples
--------

[](#examples)

Here's a basic merge where the common ancestor is a blank slate, and both descendants make additive, non-conflicting changes:

```
$a = $b = $original = [];
$a['a'] = 'apple';
$b['b'] = 'banana';

JWadhams\MergeATrois::merge($original, $a, $b);
//['a'=>'apple', 'b'=>'banana']
```

If both children make conflicting changes, the second change wins.

```
$a = $b = $original = [];
$a['a'] = 'apple';
$b['a'] = 'avocado';

JWadhams\MergeATrois::merge($original, $a, $b);
//['a'=>'avocado']
```

Changes within complex associative arrays are merged recursively.

```
$a = $b = $original = [
  'person' => ['first_name' => 'Marge', 'last_name' => 'Bouvier'],
  'hobby' => ['type' => 'bowling', 'rank' => 'novice'],
];
$a['person']['last_name'] = 'Simpson';
$b['hobby']['rank'] = 'champion';

JWadhams\MergeATrois::merge($original, $a, $b);
/*[
  "person" => [
    "first_name" => "Marge",
    "last_name" => "Simpson",
  ],
  "hobby" => [
    "type" => "bowling",
    "rank" => "champion",
  ],
]*/
```

When merging numeric arrays, the algorithm looks for unique content, and keys are ignored. Here, a and b both introduce new content in index 0, the algorithm keeps both contributions.

```
$a = $b = $original = [];
$a[] = 'apple';
$b[] = 'banana';

JWadhams\MergeATrois::merge($original, $a, $b);
//['apple', 'banana']
```

In this example, the algorithm internally acts as if the string 'apple' was deleted, and a new string 'APPLE' was added.

```
$a = $b = $original = ['apple'];
$a[0] = 'APPLE';
$b[] = 'banana';

JWadhams\MergeATrois::merge($original, $a, $b);
//['APPLE', 'banana']
```

A cardinal rule of this library is that when in doubt, the most recent change wins. So the returned numeric array will follow B's sequence, then append new content from A.

In this example, we prepend content to B, and the merge result honor's B's order.

```
$a = $b = $original = ['apple'];
array_unshift($b, 'banana');
// $b now equals ['banana', 'apple']

JWadhams\MergeATrois::merge($original, $a, $b);
//['banana', 'apple']
```

In this example, the content is prepended to A. The merge result honors B's order, except to notice A's deletion. Then the merge appends new content in A that B is unaware of.

```
$a = $b = $original = ['zucchini'];
unset($a[0]); //$a equals []
array_unshift($a, 'apple'); //$a equals ['apple']
array_unshift($b, 'banana'); //$b equals ['banana', 'zucchini']

JWadhams\MergeATrois::merge($original, $a, $b);
//['banana', 'apple']
```

**Note** the merge algorithm defers to the PHP `json_encode` method to decide what is a numeric array: if `json_encode` uses JSON's `[]` representation, we treat the array as keys-don't-matter. If your array is encoded like `{}`, it will be processed as an associative array, and indices *will* be preserved.

(`json_encode` appears to look for sequential numeric zero-indexed keys with no gaps.)

```
$a = $b = $original = [2 => 'banana', 26 => 'zucchini'];
JWadhams\MergeATrois::merge($original, $a, $b);
//[2 => 'banana', 26 => 'zucchini']
```

Installation
------------

[](#installation)

The best way to install this library is via [Composer](https://getcomposer.org/):

```
composer require jwadhams/merge-a-trois
```

If that doesn't suit you, and you want to manage updates yourself, the entire library is self-contained in `src/JWadhams/MergeATrois.php` and you can download it straight into your project as you see fit.

```
curl -O https://raw.githubusercontent.com/jwadhams/merge-a-trois/master/src/JWadhams/MergeATrois.php
```

Special Thanks
--------------

[](#special-thanks)

Thanks to [Lukas Benes](https://github.com/falsecz) — This library started life as a port of your excellent CoffeeScript library [3-way-merge](https://github.com/falsecz/3-way-merge)

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance16

Infrequent updates — may be unmaintained

Popularity33

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity78

Established project with proven stability

 Bus Factor1

Top contributor holds 100% 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

Every ~300 days

Recently: every ~574 days

Total

9

Last Release

1229d ago

Major Versions

1.1.3 → 2.0.02023-02-20

PHP version history (2 changes)1.0.0PHP &gt;=5.4.0

2.0.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/a20e6e10ced1433e8f6c8a137c3abd3897ed284d1c0e397ad656894b93ae9d47?d=identicon)[jwadhams](/maintainers/jwadhams)

---

Top Contributors

[![jwadhams](https://avatars.githubusercontent.com/u/1613739?v=4)](https://github.com/jwadhams "jwadhams (7 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jwadhams-merge-a-trois/health.svg)

```
[![Health](https://phpackages.com/badges/jwadhams-merge-a-trois/health.svg)](https://phpackages.com/packages/jwadhams-merge-a-trois)
```

###  Alternatives

[aluguest/ical-easy-reader

An easy to understood class, loads a "ics" format string and returns an array with the traditional iCal fields.

122.8k](/packages/aluguest-ical-easy-reader)

PHPackages © 2026

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