Conflicten afhandelen
Een conflict treedt op wanneer beide kanten van een samenvoeging dezelfde gegevens op onverenigbare wijze wijzigen. merql detecteert vier soorten conflicten en biedt automatische en handmatige oplossingsstrategieën.
$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";
}
}
Soorten conflicten
update_update
Beide kanten hebben dezelfde kolom van dezelfde rij in verschillende waarden gewijzigd. Dit is het meest voorkomende type conflict.
Base: posts #42 title = "Hello"
Ours: posts #42 title = "Welcome"
Theirs: posts #42 title = "Greetings"
Het conflict vermeldt: tabel posts, rijsleutel 42, kolom title, onze waarde Welcome, hun waarde Greetings, basiswaarde Hello.
update_delete
De ene kant heeft een rij bijgewerkt, de andere heeft deze verwijderd. merql kan niet bepalen of de wijziging of de verwijdering moet winnen.
Ours: posts #42 title = "Updated Title" (updated)
Theirs: posts #42 (deleted)
Het conflict vermeldt: tabel posts, rijsleutel 42, type update_delete. De waarde van onze kant bevat de volledige bijgewerkte rij. De waarde van hun kant is null.
delete_update
Het omgekeerde van update_delete. Zij hebben de rij bijgewerkt, wij hebben deze verwijderd.
Ours: posts #42 (deleted)
Theirs: posts #42 title = "Updated Title" (updated)
Het conflict vermeldt: type delete_update. De waarde van onze kant is null, de waarde van hun kant bevat de volledige bijgewerkte rij.
insert_insert
Beide kanten hebben een rij met dezelfde primaire sleutel ingevoegd. Omdat beide rijen nieuw zijn (niet aanwezig in de basis), kan merql ze niet samenvoegen.
Base: (no row #99)
Ours: posts #99 { title: "My Post" }
Theirs: posts #99 { title: "Their Post" }
Het conflict vermeldt: type insert_insert. De waarde van onze kant bevat onze rijgegevens, de waarde van hun kant bevat hun rijgegevens.
De klasse Conflict
Elk conflict is een alleen-lezen object met deze accessors:
$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)
De methode column() retourneert een kolomnaam voor conflicten van het type update_update en null voor structurele conflicten (update_delete, delete_update, insert_insert).
De primaire sleutel decoderen
De rijsleutel is een gecodeerde string. Geef de identiteitskolommen mee om een benoemde array met primaire sleutels te verkrijgen:
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']
$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
Automatisch oplossen met ConflictPolicy
De ConflictResolver past één beleid toe om alle conflicten tegelijk op te lossen:
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
| Waarde | Gedrag |
|---|---|
OursWins |
Gebruik onze waarde voor update_update. Behoud de update voor update_delete. Behoud de verwijdering voor delete_update. Gebruik onze invoeging voor insert_insert. |
TheirsWins |
Gebruik hun waarde voor update_update. Verwijder voor update_delete. Behoud de update voor delete_update. Gebruik hun invoeging voor insert_insert. |
Manual |
Retourneer het resultaat ongewijzigd. Alle conflicten blijven onopgelost. |
Hoe het oplossen werkt
Voor conflicten van het type update_update past de resolver de bestaande samenvoegbewerking voor die rij aan en vervangt de conflicterende kolomwaarde door de winnende waarde.
Voor structurele conflicten (update_delete, delete_update, insert_insert) voegt de resolver een nieuwe bewerking toe aan het resultaat:
update_deletemetOursWinsvoegt een UPDATE-bewerking toe.update_deletemetTheirsWinsvoegt een DELETE-bewerking toe.delete_updatemetOursWinsvoegt een DELETE-bewerking toe.delete_updatemetTheirsWinsvoegt een UPDATE-bewerking toe.insert_insertgebruikt de rijwaarden van de winnende kant.
Het opgeloste MergeResult heeft een lege conflictenlijst en kan direct worden toegepast.
Handmatig oplossen
Voor gedetailleerde controle inspecteer je elk conflict en bouw je je eigen oplossing:
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";
}
}
Toepassen met onopgeloste conflicten
De Applier weigert een samenvoegresultaat met onopgeloste conflicten toe te passen. Deze gooit een ConflictException:
use Merql\Exceptions\ConflictException;
try {
$applier->apply($result);
} catch (ConflictException $e) {
echo $e->getMessage();
// "Cannot apply merge with 3 unresolved conflicts"
}
Los conflicten altijd op voordat je ze toepast, met ConflictResolver::resolve() of door handmatig een schoon MergeResult te maken.