Gestionarea conflictelor
Un conflict apare când ambele părți ale unei îmbinări modifică aceleași date în moduri incompatibile. merql detectează patru tipuri de conflicte și oferă strategii de rezolvare automate și manuale.
$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";
}
}
Tipuri de conflicte
update_update
Ambele părți au modificat aceeași coloană din același rând la valori diferite. Acesta este cel mai frecvent tip de conflict.
Base: posts #42 title = "Hello"
Ours: posts #42 title = "Welcome"
Theirs: posts #42 title = "Greetings"
Conflictul raportează: tabelul posts, cheia rândului 42, coloana title, valoarea noastră Welcome, valoarea lor Greetings, valoarea de bază Hello.
update_delete
O parte a actualizat un rând, iar cealaltă l-a șters. merql nu poate determina dacă trebuie păstrată actualizarea sau ștergerea.
Ours: posts #42 title = "Updated Title" (updated)
Theirs: posts #42 (deleted)
Conflictul raportează: tabelul posts, cheia rândului 42, tipul update_delete. Valoarea noastră conține întregul rând actualizat. Valoarea lor este null.
delete_update
Inversul lui update_delete. Ei au actualizat rândul, iar noi l-am șters.
Ours: posts #42 (deleted)
Theirs: posts #42 title = "Updated Title" (updated)
Conflictul raportează: tipul delete_update. Valoarea noastră este null, iar valoarea lor conține întregul rând actualizat.
insert_insert
Ambele părți au inserat un rând cu aceeași cheie primară. Deoarece ambele rânduri sunt noi (nu existau în baza de date), merql nu le poate îmbina.
Base: (no row #99)
Ours: posts #99 { title: "My Post" }
Theirs: posts #99 { title: "Their Post" }
Conflictul raportează: tipul insert_insert. Valoarea noastră conține datele rândului nostru, iar valoarea lor conține datele rândului lor.
Clasa Conflict
Fiecare conflict este un obiect numai pentru citire cu următoarele accesorii:
$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)
Metoda column() returnează numele unei coloane pentru conflictele update_update și null pentru conflictele structurale (update_delete, delete_update, insert_insert).
Decodarea cheii primare
Cheia rândului este un șir codificat. Pentru a obține un tablou cu numele cheilor primare, transmiteți coloanele de identitate:
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']
$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
Rezolvare automată cu ConflictPolicy
ConflictResolver aplică o politică pentru a rezolva toate conflictele dintr-o dată:
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);
Enum ConflictPolicy
| Valoare | Comportament |
|---|---|
OursWins |
Folosește valoarea noastră pentru update_update. Păstrează actualizarea pentru update_delete. Păstrează ștergerea pentru delete_update. Folosește inserarea noastră pentru insert_insert. |
TheirsWins |
Folosește valoarea lor pentru update_update. Șterge pentru update_delete. Păstrează actualizarea pentru delete_update. Folosește inserarea lor pentru insert_insert. |
Manual |
Returnează rezultatul neschimbat. Toate conflictele rămân nerezolvate. |
Cum funcționează rezolvarea
Pentru conflictele update_update, rezolvatorul modifică operația de îmbinare existentă pentru acel rând, înlocuind valoarea coloanei aflate în conflict cu valoarea părții câștigătoare.
Pentru conflictele structurale (update_delete, delete_update, insert_insert), rezolvatorul adaugă o operație nouă la rezultat:
update_deletecuOursWinsadaugă o operație UPDATE.update_deletecuTheirsWinsadaugă o operație DELETE.delete_updatecuOursWinsadaugă o operație DELETE.delete_updatecuTheirsWinsadaugă o operație UPDATE.insert_insertfolosește valorile rândului de pe partea câștigătoare.
Rezultatul MergeResult rezolvat are o listă de conflicte goală și poate fi aplicat direct.
Rezolvare manuală
Pentru un control detaliat, inspectați fiecare conflict și construiți propria rezolvare:
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";
}
}
Aplicarea cu conflicte nerezolvate
Applier refuză să aplice un rezultat al îmbinării care conține conflicte nerezolvate. Aruncă o excepție ConflictException:
use Merql\Exceptions\ConflictException;
try {
$applier->apply($result);
} catch (ConflictException $e) {
echo $e->getMessage();
// "Cannot apply merge with 3 unresolved conflicts"
}
Rezolvați întotdeauna conflictele înainte de aplicare, fie cu ConflictResolver::resolve(), fie construind manual un MergeResult curat.