Ga naar de inhoud

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.

PHP
$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.

Code
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.

Code
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.

Code
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.

Code
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:

PHP
$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:

PHP
$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:

PHP
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_delete met OursWins voegt een UPDATE-bewerking toe.
  • update_delete met TheirsWins voegt een DELETE-bewerking toe.
  • delete_update met OursWins voegt een DELETE-bewerking toe.
  • delete_update met TheirsWins voegt een UPDATE-bewerking toe.
  • insert_insert gebruikt 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:

PHP
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:

PHP
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.