Konfliktbehandlung
Ein Konflikt entsteht, wenn beide Seiten einer Zusammenführung dieselben Daten auf unvereinbare Weise ändern. merql erkennt vier Konflikttypen und bietet Strategien zur automatischen und manuellen Lösung.
$result = $merge->merge($base, $ours, $theirs);
if (!$result->isClean()) {
foreach ($result->conflicts() as $conflict) {
echo "{$conflict->table()}.{$conflict->column()}: "
. "{$conflict->oursValue()} vs {$conflict->theirsValue()}\n";
}
}
Konflikttypen
update_update
Beide Seiten haben dieselbe Spalte derselben Zeile auf unterschiedliche Werte geändert. Dies ist der häufigste Konflikttyp.
Base: posts #42 title = "Hello"
Ours: posts #42 title = "Welcome"
Theirs: posts #42 title = "Greetings"
Der Konflikt meldet: Tabelle posts, Zeilenschlüssel 42, Spalte title, Wert unserer Seite Welcome, Wert der Gegenseite Greetings, Basiswert Hello.
update_delete
Eine Seite hat eine Zeile aktualisiert, die andere hat sie gelöscht. merql kann nicht bestimmen, ob die Aktualisierung oder das Löschen Vorrang haben soll.
Ours: posts #42 title = "Updated Title" (updated)
Theirs: posts #42 (deleted)
Der Konflikt meldet: Tabelle posts, Zeilenschlüssel 42, Typ update_delete. Der Wert unserer Seite enthält die vollständig aktualisierte Zeile. Der Wert der Gegenseite ist null.
delete_update
Die Umkehrung von update_delete. Die Gegenseite hat die Zeile aktualisiert, unsere Seite hat sie gelöscht.
Ours: posts #42 (deleted)
Theirs: posts #42 title = "Updated Title" (updated)
Der Konflikt meldet: Typ delete_update. Der Wert unserer Seite ist null, der Wert der Gegenseite enthält die vollständig aktualisierte Zeile.
insert_insert
Beide Seiten haben eine Zeile mit demselben Primärschlüssel eingefügt. Da beide Zeilen neu sind und in der Basis nicht vorkommen, kann merql sie nicht zusammenführen.
Base: (no row #99)
Ours: posts #99 { title: "My Post" }
Theirs: posts #99 { title: "Their Post" }
Der Konflikt meldet: Typ insert_insert. Der Wert unserer Seite enthält die Zeilendaten unserer Seite, der Wert der Gegenseite enthält deren Zeilendaten.
Die Conflict-Klasse
Jeder Konflikt ist ein schreibgeschütztes Objekt mit folgenden Zugriffsmethoden:
$conflict->table(); // string: table name
$conflict->rowKey(); // string: encoded row identity key
$conflict->type(); // string: "update_update"|"update_delete"|"delete_update"|"insert_insert"
$conflict->column(); // ?string: column name (null for structural conflicts)
$conflict->oursValue(); // mixed: our value (null for delete side, full row for structural)
$conflict->theirsValue(); // mixed: their value (null for delete side, full row for structural)
$conflict->baseValue(); // mixed: base value (null for inserts)
Die Methode column() gibt bei Konflikten vom Typ update_update einen Spaltennamen und bei strukturellen Konflikten null zurück (update_delete, delete_update, insert_insert).
Primärschlüssel decodieren
Der Zeilenschlüssel ist eine codierte Zeichenfolge. Um ein Array mit benannten Primärschlüsseln zu erhalten, übergib die Identitätsspalten:
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']
$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
Automatische Lösung mit ConflictPolicy
ConflictResolver wendet eine Richtlinie an, um alle Konflikte auf einmal zu lösen:
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;
// One side wins everywhere.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);
// Manual: return the result unchanged (inspect conflicts yourself).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
ConflictPolicy-Enum
| Wert | Verhalten |
|---|---|
OursWins |
Verwendet bei update_update den Wert unserer Seite. Behält bei update_delete die Aktualisierung bei. Behält bei delete_update das Löschen bei. Verwendet bei insert_insert den Eintrag unserer Seite. |
TheirsWins |
Verwendet bei update_update den Wert der Gegenseite. Löscht bei update_delete. Behält bei delete_update die Aktualisierung bei. Verwendet bei insert_insert den Eintrag der Gegenseite. |
Manual |
Gibt das Ergebnis unverändert zurück. Alle Konflikte bleiben ungelöst. |
Ablauf der Konfliktlösung
Bei Konflikten vom Typ update_update ändert der Resolver die vorhandene Zusammenführungsoperation für die Zeile und ersetzt den Wert der betroffenen Spalte durch den Wert der Seite, die Vorrang hat.
Bei strukturellen Konflikten (update_delete, delete_update, insert_insert) fügt der Resolver dem Ergebnis eine neue Operation hinzu:
update_deletemitOursWinsfügt eine UPDATE-Operation hinzu.update_deletemitTheirsWinsfügt eine DELETE-Operation hinzu.delete_updatemitOursWinsfügt eine DELETE-Operation hinzu.delete_updatemitTheirsWinsfügt eine UPDATE-Operation hinzu.insert_insertverwendet die Zeilenwerte der Seite, die Vorrang hat.
Das gelöste MergeResult hat eine leere Konfliktliste und kann direkt angewendet werden.
Manuelle Lösung
Um gezielt vorzugehen, prüfe jeden Konflikt einzeln und erstelle deine eigene Lösung:
foreach ($result->conflicts() as $conflict) {
echo "Table: {$conflict->table()}\n";
echo "Row: {$conflict->rowKey()}\n";
echo "Type: {$conflict->type()}\n";
if ($conflict->column() !== null) {
echo "Column: {$conflict->column()}\n";
echo "Base: " . var_export($conflict->baseValue(), true) . "\n";
echo "Ours: " . var_export($conflict->oursValue(), true) . "\n";
echo "Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
}
}
Anwenden mit ungelösten Konflikten
Applier wendet kein Zusammenführungsergebnis mit ungelösten Konflikten an. Dabei wird ein ConflictException ausgelöst:
use Merql\Exceptions\ConflictException;
try {
$applier->apply($result);
} catch (ConflictException $e) {
echo $e->getMessage();
// "Cannot apply merge with 3 unresolved conflicts"
}
Löse Konflikte immer vor dem Anwenden, entweder mit ConflictResolver::resolve() oder indem du manuell ein konfliktfreies MergeResult erstellst.