Zum Inhalt springen

Zusammenführungspläne

Ein MergeResult ist die ausführbare Ausgabe einer Zusammenführung. Ein MergePlan ist deren prüfbare Form. Verwende ihn, wenn eine Anwendung Datenbankänderungen vor dem Schreiben in die Zieldatenbank anzeigen, bereitstellen, serialisieren oder gezielt anwenden muss.

PHP
use Merql\Merql;

$plan = Merql::plan(
    id: 'plan-2026-06-06',
    base: 'base',
    ours: 'ours',
    theirs: 'theirs',
    metadata: ['sandbox_id' => 'ow_123'],
);

Merql::plan() lädt die drei benannten Snapshots und führt sie auf Spaltenebene zusammen, wie Merql::merge(). Für einen Plan, der die Zusammenführung auf Zellenebene für TEXT- und JSON-Spalten nutzt, erstelle das Ergebnis selbst und übergib es an MergePlanBuilder:

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);

Der Plan enthält:

  • die zum Erstellen verwendeten Snapshot-Namen,
  • eine Zusammenfassung der Operationen, Konflikte, Schemaabweichungen, Tabellen und Gruppen,
  • stabile Operations-IDs,
  • stabile Änderungsgruppen-IDs,
  • Zeilenidentitätsspalten und Identitätswerte,
  • Zeilenwerte für Basis, unsere Seite, Gegenseite und Ergebnis für jede Operation,
  • optionale Metadaten der Host-Anwendung,
  • einen Plan-Hash,
  • eine Schema-Version.

Stabile IDs

Operations-IDs sind bei identischem Operationsinhalt deterministisch. Sie werden aus dem Tabellennamen, Zeilenschlüssel, Operationstyp, der Operationsquelle, den geänderten Spalten und den Ergebniswerten abgeleitet.

Änderungsgruppen-IDs sind bei identischem Gruppeninhalt deterministisch. Der Standard-Builder erstellt eine Operationsgruppe pro Operation. Host-Anwendungen können aus der Operationsliste größere, domänenspezifische Gruppen erstellen und sie dennoch mit derselben DTO-Form serialisieren.

PHP
foreach ($plan->changeGroups as $group) {
    echo $group->id . ': ' . implode(', ', $group->operationIds);
}

Operationsstruktur

Jede MergePlanOperation enthält genügend Kontext für Prüfung und Wiederherstellung.

PHP
$operation = $plan->operations[0];

$operation->id;              // stable operation ID
$operation->type;            // insert, update, delete
$operation->table;           // canonical table name
$operation->rowKey;          // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues;  // decoded identity values
$operation->source;          // ours, theirs, or merged
$operation->baseRow;         // row in base snapshot, or null
$operation->oursRow;         // row in ours snapshot, or null
$operation->theirsRow;       // row in theirs snapshot, or null
$operation->resultValues;    // values the merge would write
$operation->changedColumns;  // changed result columns

Selections

Verwende ChangeGroupSelection, wenn der UI Gruppen bereitstellt. Wandle ihn in ein OperationSelection um, wenn du Änderungen anwendest oder Artefakte für die Wiederherstellung erzeugst.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$operationSelection = $selection->toOperationSelection($plan);

$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    $operationSelection,
    SnapshotStore::load($plan->baseSnapshot),
);

Das ausgewählte Ergebnis ist ein konfliktfreies MergeResult, das nur ausgewählte Operationen enthält. Es kann an Merql::apply() oder Merql::applyGuarded() übergeben werden.

Auswählen, was einem Ziel noch fehlt

Ein Plan enthält die Operationen beider Seiten, da das Zusammenführungsergebnis die Basis in den zusammengeführten Zustand überführt. Wenn das Ziel bereits eine Seite enthält, wähle nur die Operationen aus, die dort noch fehlen. Für ein Ziel mit dem Stand der Gegenseite, etwa eine Live-Datenbank, die Änderungen aus einer Sandbox-Kopie erhält, sind das alle Operationen mit der Quelle ours sowie merged-Aktualisierungen, die die Spaltenänderungen beider Seiten kombinieren:

PHP
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

// Rows with an unresolved conflict stay out of the selection.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load($plan->baseSnapshot),
);

Operationen mit der Quelle theirs sowie merged-Löschungen (Zeilen, die beide Seiten gelöscht haben) sind in diesem Ziel bereits enthalten.

Ein Plan aus einer Zusammenführung mit Konflikten führt weiterhin für jede konfliktbehaftete Zeile, die beide Seiten aktualisiert haben, eine Operation auf. Die Ergebniswerte enthalten für die Konfliktspalte den Wert unserer Seite, und SelectedMergeResultFactory gibt für jede Auswahl ein konfliktfreies Ergebnis zurück. Die conflictState der Operation und Änderungsgruppe lauten derzeit auch dann clean (#9). Prüfe daher wie oben selbst $plan->conflicts oder löse die Konflikte mit ConflictResolver und erstelle den Plan mit MergePlanBuilder aus dem gelösten Ergebnis. Unter Guarded Apply erfährst du, wie du die Auswahl mit Vorbedingungen für aktuelle Zeilen schreibst.

Serialisierung

Zusammenführungspläne und Auswahlen lassen sich mit stabiler JSON-Serialisierung speichern. Speichere den serialisierten Plan, wenn ein späterer Anwendungsschritt genau den geprüften Operationssatz verwenden muss.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\MergePlanSerializer;

$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);

$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
    json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);

Alias-Snapshots

Zusammenführungspläne verwenden kanonische Tabellennamen. Wenn sich physische Tabellennamen zwischen Umgebungen unterscheiden, erfasse sie vor der Planung unter demselben kanonischen Namen.

PHP
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

SnapshotStore::save($snapshotter->captureAliased('live', [
    'wp_posts' => 'wp_posts',
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abc_posts' => 'wp_posts',
]));

Das im Snapshot gespeicherte Schema verwendet den kanonischen Tabellennamen und bewahrt dabei die Spalten, Schlüssel, Identitätsspalten, Zeilenschlüssel und Filter der physischen Tabelle.