\Drupal\strata\Branch ThreeWayMerge

Merges one configuration object across a base, a target and a branch.

This works because configuration is captured WHOLE. Every save records the object's complete raw data, so its value at the merge base, at the target tip and at the branch tip are three complete documents and comparing them is an ordinary operation. Content entities and table rows are captured as field deltas against a parent, so the same operation over them would mean replaying two divergent delta chains and inventing a value wherever they disagree. Nothing here is given a non-configuration realm, and Merger refuses one by name.

The comparison runs key by key and recurses into nested maps, so two people editing different settings of the same object both get their edit. It stops recursing at a list. A sequential array carries meaning in its order and not in its indices, so merging two lists position by position would splice the third item of one into the third slot of the other; a list both sides changed is one value in disagreement, which is a conflict.

A conflict is never resolved on its own. Two values written for the same key is a disagreement between two people, and the only safe automatic answer is to stop and show both. A strategy the caller names can decide them, and it decides only the keys that actually collided.

Summary

Methods
Properties
Constants
merge
No public properties found
MAX_DEPTH
No protected methods found
No protected properties found
No protected constants found
existence
wholeObjectConflict
mergeMaps
entry
isMergeableMap
same
normalize
paths
set
No private properties found
No private constants found

Constant

MAX_DEPTH

MAX_DEPTH = 32

How deep the key-by-key comparison recurses before treating a value as one lump.

Configuration nests a handful of levels at most; anything past this is a structure that will be compared whole, which is conservative rather than wrong.

Methods

merge()

merge(string  $name, array|null  $base, array|null  $ours, array|null  $theirs, \Drupal\strata\Branch\MergeStrategy  $strategy = \Drupal\strata\Branch\MergeStrategy::REFUSE) : \Drupal\strata\Branch\MergeEntry

Merges one object.

Parameters

string $name

The configuration object name.

array|null $base

Its data at the merge base, or NULL when it did not exist there.

array|null $ours

Its data at the target tip, or NULL when it does not exist there.

array|null $theirs

Its data at the branch tip, or NULL when it does not exist there.

\Drupal\strata\Branch\MergeStrategy $strategy

What to do with a key both sides changed.

Returns

\Drupal\strata\Branch\MergeEntry —

What the merge decided.

existence()

existence(string  $name, array|null  $base, array|null  $ours, array|null  $theirs, \Drupal\strata\Branch\MergeStrategy  $strategy) : \Drupal\strata\Branch\MergeEntry|null

The entry for an object that does not exist on all three sides.

Creation and removal are decided before any key is looked at, because a key-by-key comparison against a document that is not there would read every key as an addition.

Parameters

string $name

The configuration object name.

array|null $base

Its data at the merge base.

array|null $ours

Its data at the target tip.

array|null $theirs

Its data at the branch tip.

\Drupal\strata\Branch\MergeStrategy $strategy

What to do with a disagreement.

Returns

\Drupal\strata\Branch\MergeEntry|null —

The entry, or NULL when all three sides have the object and the keys decide it.

wholeObjectConflict()

wholeObjectConflict(string  $name, array|null  $base, array|null  $ours, array|null  $theirs, \Drupal\strata\Branch\MergeStrategy  $strategy) : \Drupal\strata\Branch\MergeEntry

A conflict over whether the object exists at all.

Parameters

string $name

The configuration object name.

array|null $base

Its data at the merge base.

array|null $ours

Its data at the target tip.

array|null $theirs

Its data at the branch tip.

\Drupal\strata\Branch\MergeStrategy $strategy

What to do with the disagreement.

Returns

\Drupal\strata\Branch\MergeEntry —

The entry, resolved to one side's whole value when a strategy named one.

mergeMaps()

mergeMaps(array  $base, array  $ours, array  $theirs, string  $prefix, int  $depth) : array{value: array<string,mixed>, conflicts: array<string,array{ours: mixed, theirs: mixed}>, theirs: list<string>, ours: list<string>}

Merges two maps against their base, key by key.

Parameters

array $base

The map at the merge base.

array $ours

The map at the target tip.

array $theirs

The map at the branch tip.

string $prefix

Dotted path of the map inside the object, empty at the top.

int $depth

How far the recursion has gone.

Returns

array{value: array, conflicts: array, theirs: list, ours: list} —

The merged map, the keys that collided with both values, and the paths each side moved.

entry()

entry(string  $name, array{value: array, conflicts: array, theirs: list, ours: list}  $merged, \Drupal\strata\Branch\MergeStrategy  $strategy) : \Drupal\strata\Branch\MergeEntry

The entry a merged map produces, once a strategy has had its say.

Parameters

string $name

The configuration object name.

array{value: array, conflicts: array, theirs: list, ours: list} $merged

What the key comparison produced.

\Drupal\strata\Branch\MergeStrategy $strategy

What to do with a key both sides changed.

Returns

\Drupal\strata\Branch\MergeEntry —

The entry.

isMergeableMap()

isMergeableMap(mixed  $value) : bool

Whether a value is a map this merges into rather than compares whole.

Parameters

mixed $value

The value.

Returns

bool —

TRUE for a non-empty associative array.

same()

same(mixed  $left, mixed  $right) : bool

Whether two values are the same for merge purposes.

Compared by identity rather than equality, so 0, '', false and null stay four different settings. Arrays are compared after sorting by key at every level, because two saves that wrote the same settings in a different order wrote the same configuration.

Parameters

mixed $left

One value.

mixed $right

The other.

Returns

bool —

TRUE when they are the same.

normalize()

normalize(array  $value, int  $depth = 0) : array<array-key,mixed>

An array with every map level sorted by key.

Parameters

array $value

The array.

int $depth

Recursion guard.

Returns

array

The array, key-sorted at every level. A list keeps its order, which is its meaning.

paths()

paths(array  $value, string  $prefix, int  $depth) : list<string>

Every dotted path a map holds, stopping at a leaf.

Parameters

array $value

The map.

string $prefix

Dotted path of the map, empty at the top.

int $depth

Recursion guard.

Returns

list

The paths.

set()

set(array  $value, string  $path, mixed  $item) : array<string,mixed>

A map with one dotted path set to a value.

Parameters

array $value

The map.

string $path

The dotted path.

mixed $item

What to set it to.

Returns

array

The map.