冲突处理
合并双方以不兼容的方式修改了相同数据时,就会发生冲突。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)
对于 update_update 冲突,column() 方法会返回列名;对于结构性冲突(update_delete、delete_update、insert_insert),则返回 null。
解码主键
行键是经过编码的字符串。要获取带名称的主键数组,请传入标识列:
$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。