
# Filtres

Les filtres déterminent les données incluses dans un instantané. Excluez les tables de cache, ignorez les colonnes d’horodatage variables ou écartez les lignes temporaires. Les filtres sont appliqués lors de la capture et affectent toutes les opérations suivantes (diff, fusion, application).

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## TableFilter

Détermine les tables incluses dans un instantané. Prend en charge les motifs glob via le `fnmatch()` de PHP.

### Exclure des tables

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['sessions', 'cache_*', 'wp_*_log']);
```

Toute table correspondant à un motif d’exclusion est omise de l’instantané. Le caractère générique `*` correspond à toute séquence de caractères.

### Inclure des tables

```php
$filter = TableFilter::include(['posts', 'comments', 'users']);
```

Seules les tables correspondant à un motif d’inclusion sont capturées. Toutes les autres sont exclues.

### Fonctionnement

La méthode `apply()` du filtre prend une liste de noms de tables et renvoie le sous-ensemble filtré :

```php
$allTables = ['posts', 'comments', 'sessions', 'cache_pages', 'users'];

$filter = TableFilter::exclude(['sessions', 'cache_*']);
$filtered = $filter->apply($allTables);
// ['posts', 'comments', 'users']

$filter = TableFilter::include(['posts', 'users']);
$filtered = $filter->apply($allTables);
// ['posts', 'users']
```

Lorsque des motifs d’inclusion et d’exclusion sont présents (dans des instances de filtre distinctes), l’exclusion est évaluée en premier. Pour être conservée, une table ne doit correspondre à aucun motif d’exclusion et doit correspondre à au moins un motif d’inclusion.

## ColumnFilter

Supprime des colonnes spécifiques des données de ligne lors de la capture. Utile pour les colonnes variables qui changent à chaque écriture et provoqueraient de faux écarts.

```php
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'modified_date', 'last_login']);
```

### Cas d’usage courants

- Horodatages `updated_at` / `modified_at` qui changent à chaque enregistrement.
- Compteurs `revision` qui s’incrémentent automatiquement.
- `session_token` ou autres champs temporaires.

### Fonctionnement

Le filtre supprime les colonnes nommées de chaque ligne après le calcul de la clé de ligne :

```php
$filter = ColumnFilter::ignore(['updated_at']);

$row = ['id' => 1, 'title' => 'Hello', 'updated_at' => '2026-01-15 10:30:00'];
$filtered = $filter->applyToRow($row);
// ['id' => 1, 'title' => 'Hello']
```

La clé de ligne (identité) est calculée à partir de la ligne non filtrée. Le filtre de colonnes ne modifie donc jamais les colonnes d’identité. Seules les données utilisées pour l’empreinte et le calcul des écarts sont filtrées.

## RowFilter

Filtre les lignes une par une à l’aide d’une fermeture de prédicat. Celle-ci reçoit le nom de la table et toutes les données de la ligne, puis renvoie `true` pour inclure la ligne ou `false` pour l’exclure.

```php
use Merql\Filter\RowFilter;

// Exclude draft posts.
$filter = RowFilter::create(function (string $table, array $row): bool {
    if ($table === 'posts' && ($row['status'] ?? '') === 'draft') {
        return false;
    }
    return true;
});

// Exclude rows with NULL email.
$filter = RowFilter::create(function (string $table, array $row): bool {
    if ($table === 'users') {
        return $row['email'] !== null;
    }
    return true;
});
```

### Fonctionnement

La méthode `shouldInclude()` est appelée pour chaque ligne lors de la capture :

```php
$include = $filter->shouldInclude('posts', ['id' => 1, 'status' => 'draft']);
// false
```

Les lignes exclues ne font l’objet d’aucun calcul d’empreinte, ne sont pas stockées dans l’instantané et n’apparaissent ni dans les écarts ni dans les fusions.

## Combiner les filtres

Transmettez plusieurs filtres à `capture()`. Au plus un filtre de chaque type est utilisé :

```php
$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['cache_*']),
    ColumnFilter::ignore(['updated_at']),
    RowFilter::create(fn($table, $row) => ($row['active'] ?? '1') === '1'),
]);
```

Ordre de traitement :

1. **TableFilter** s’exécute en premier et détermine quelles tables capturer.
2. Les lignes sont lues dans la base de données pour chaque table.
3. **RowFilter** s’exécute sur chaque ligne et exclut celles qui ne correspondent pas.
4. La **clé de ligne** est calculée à partir de la ligne non filtrée (colonnes d’identité intactes).
5. **ColumnFilter** supprime les colonnes ignorées des données de ligne.
6. L’**empreinte** est calculée à partir des données de ligne filtrées.

Cet ordre garantit que les colonnes d’identité restent toujours disponibles pour le calcul de la clé, même si le filtre de colonnes les supprime ensuite.

## Filtres et cohérence

Les mêmes filtres doivent être utilisés pour les trois instantanés d’une fusion (base, le nôtre, le leur). Avec des filtres différents, des lignes ou des colonnes peuvent sembler avoir été ajoutées ou supprimées alors qu’elles ont simplement été filtrées différemment.

```php
// Use the same filter set for all snapshots.
$filters = [
    TableFilter::exclude(['sessions']),
    ColumnFilter::ignore(['updated_at']),
];

$base   = $snapshotter->capture('base', filters: $filters);
$ours   = $snapshotter->capture('ours', filters: $filters);
$theirs = $snapshotter->capture('theirs', filters: $filters);
```
