
# Обработване на конфликти

Конфликт възниква, когато двете страни в сливането променят едни и същи данни по несъвместим начин. merql открива четири типа конфликти и предлага автоматични и ръчни стратегии за разрешаването им.

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

## Типове конфликти

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

Всеки конфликт е обект само за четене със следните средства за достъп:

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

Методът `column()` връща името на колона за конфликти `update_update` и `null` за структурни конфликти (`update_delete`, `delete_update`, `insert_insert`).

### Декодиране на първичния ключ

Ключът на реда е кодиран низ. За да получите масив с именуван първичен ключ, подайте колоните за идентификация:

```php
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']

$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
```

## Автоматично разрешаване с ConflictPolicy

`ConflictResolver` прилага правило, за да разреши всички конфликти наведнъж:

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

| Стойност | Поведение |
|---|---|
| `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` има празен списък с конфликти и може да бъде приложен директно.

## Ръчно разрешаване

За по-прецизен контрол проверете всеки конфликт и създайте собствено решение:

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

## Прилагане при неразрешени конфликти

`Applier` отказва да приложи резултат от сливане с неразрешени конфликти. Хвърля `ConflictException`:

```php
use Merql\Exceptions\ConflictException;

try {
    $applier->apply($result);
} catch (ConflictException $e) {
    echo $e->getMessage();
    // "Cannot apply merge with 3 unresolved conflicts"
}
```

Винаги разрешавайте конфликтите преди прилагане — с `ConflictResolver::resolve()` или като създадете ръчно изчистен `MergeResult`.
