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

```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";
    }
}
```

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

```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)
```

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:

```php
$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ă:

```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);
```

### 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_delete` cu `OursWins` adaugă o operație UPDATE.
- `update_delete` cu `TheirsWins` adaugă o operație DELETE.
- `delete_update` cu `OursWins` adaugă o operație DELETE.
- `delete_update` cu `TheirsWins` adaugă o operație UPDATE.
- `insert_insert` foloseș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:

```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";
    }
}
```

## Aplicarea cu conflicte nerezolvate

`Applier` refuză să aplice un rezultat al îmbinării care conține conflicte nerezolvate. Aruncă o excepție `ConflictException`:

```php
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.
