Обработване на конфликти
Конфликт възниква, когато двете страни в сливането променят едни и същи данни по несъвместим начин. merql открива четири типа конфликти и предлага автоматични и ръчни стратегии за разрешаването им.
$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";
}
}
Типове конфликти
update_update
И двете страни са променили една и съща колона в един и същи ред с различни стойности. Това е най-често срещаният тип конфликт.
Base: posts #42 title = "Hello"
Ours: posts #42 title = "Welcome"
Theirs: posts #42 title = "Greetings"
Конфликтът съдържа: таблица posts, ключ на ред 42, колона title, наша стойност Welcome, тяхна стойност Greetings, базова стойност Hello.
update_delete
Едната страна е актуализирала ред, а другата го е изтрила. merql не може да определи дали да надделее актуализацията, или изтриването.
Ours: posts #42 title = "Updated Title" (updated)
Theirs: posts #42 (deleted)
Конфликтът съдържа: таблица posts, ключ на ред 42, тип update_delete. Нашата стойност съдържа целия актуализиран ред. Тяхната стойност е null.
delete_update
Обратният случай на update_delete. Те са актуализирали реда, а ние сме го изтрили.
Ours: posts #42 (deleted)
Theirs: posts #42 title = "Updated Title" (updated)
Конфликтът съдържа: тип delete_update. Нашата стойност е null, а тяхната съдържа целия актуализиран ред.
insert_insert
И двете страни са добавили ред със същия първичен ключ. Тъй като и двата реда са нови (не присъстват в базата), merql не може да ги слее.
Base: (no row #99)
Ours: posts #99 { title: "My Post" }
Theirs: posts #99 { title: "Their Post" }
Конфликтът съдържа: тип insert_insert. Нашата стойност съдържа данните за нашия ред, а тяхната — данните за техния ред.
Класът Conflict
Всеки конфликт е обект само за четене със следните средства за достъп:
$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)
Методът column() връща името на колона за конфликти update_update и null за структурни конфликти (update_delete, delete_update, insert_insert).
Декодиране на първичния ключ
Ключът на реда е кодиран низ. За да получите масив с именуван първичен ключ, подайте колоните за идентификация:
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']
$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
Автоматично разрешаване с ConflictPolicy
ConflictResolver прилага правило, за да разреши всички конфликти наведнъж:
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
| Стойност | Поведение |
|---|---|
OursWins |
Използва нашата стойност за update_update. Запазва актуализацията при update_delete. Запазва изтриването при delete_update. Използва нашия запис при insert_insert. |
TheirsWins |
Използва тяхната стойност за update_update. Изтрива при update_delete. Запазва актуализацията при delete_update. Използва техния запис при insert_insert. |
Manual |
Връща резултата без промяна. Всички конфликти остават неразрешени. |
Как работи разрешаването
При конфликти update_update инструментът за разрешаване променя съществуващата операция за сливане на реда, като заменя конфликтната стойност на колоната със стойността от спечелилата страна.
При структурни конфликти (update_delete, delete_update, insert_insert) инструментът за разрешаване добавя нова операция към резултата:
update_deleteсOursWinsдобавя операция UPDATE.update_deleteсTheirsWinsдобавя операция DELETE.delete_updateсOursWinsдобавя операция DELETE.delete_updateсTheirsWinsдобавя операция UPDATE.insert_insertизползва стойностите на реда от спечелилата страна.
Решеният MergeResult има празен списък с конфликти и може да бъде приложен директно.
Ръчно разрешаване
За по-прецизен контрол проверете всеки конфликт и създайте собствено решение:
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";
}
}
Прилагане при неразрешени конфликти
Applier отказва да приложи резултат от сливане с неразрешени конфликти. Хвърля ConflictException:
use Merql\Exceptions\ConflictException;
try {
$applier->apply($result);
} catch (ConflictException $e) {
echo $e->getMessage();
// "Cannot apply merge with 3 unresolved conflicts"
}
Винаги разрешавайте конфликтите преди прилагане — с ConflictResolver::resolve() или като създадете ръчно изчистен MergeResult.