Référence PHP API
Référence complète de toutes les classes et méthodes publiques de merql.
Merql (façade)
Merql\Merql est le point d'entrée statique pour les opérations courantes. Il encapsule les classes de niveau inférieur et gère automatiquement la persistance des instantanés.
use Merql\Connection;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Merql;
$connection = Connection::sqlite(':memory:');
Merql::init($connection, identityRules: new IdentityRuleSet([
'options' => IdentityRule::natural(['option_name']),
]));
init(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null): void
Initialisez la façade avec une connexion à la base de données. Doit être appelé avant
snapshot(), apply() ou applyGuarded() ; rappelez cette méthode pour changer de base de données,
par exemple pour créer des instantanés de ours et theirs à partir de deux copies différentes. En l'absence de
$driver, la méthode appelle DriverFactory::create(), qui lève une exception pour les moteurs autres que
MySQL et SQLite, sauf si un pilote est enregistré. Fournissez un pilote pour remplacer la détection automatique, ou un ensemble de règles d'identité pour remplacer l'identité des lignes par table lors de la capture d'instantanés.
snapshot(string $name, array $tables = []): Snapshot
Capturez l'état actuel de la base de données et enregistrez-le sur disque. Passez un tableau de noms de tables pour créer un instantané de certaines tables, ou omettez-le pour inclure toutes les tables.
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
diff(string $base, string $current): Changeset
Chargez deux instantanés nommés et calculez les modifications entre eux. init() n'est pas nécessaire, car cette opération utilise les instantanés persistés.
$changeset = Merql::diff('before', 'after');
merge(string $base, string $ours, string $theirs): MergeResult
Fusion à trois voies de trois instantanés nommés. Renvoie un résultat contenant les opérations sans conflit et les éventuels conflits. La fusion s'effectue au niveau des colonnes : la façade construit ThreeWayMerge sans CellMergeConfig, la fusion au niveau des cellules n'est donc pas appliquée. Utilisez directement ThreeWayMerge pour cela.
$result = Merql::merge('base', 'ours', 'theirs');
patch(string $base, string $changes): MergeResult
Fusion à deux voies : appliquez les modifications à la base. Équivalent à merge($base, $base, $changes). Produit toujours un résultat sans conflit, car il n'y a pas de modifications concurrentes. Au niveau des colonnes uniquement, comme merge().
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
Exécutez un résultat de fusion sans conflit sur la base de données transmise à init(). Nécessite init(). Lève ConflictException si des conflits non résolus subsistent.
Toutes les opérations du résultat sont exécutées. Le résultat complet de merge() ne s'applique donc correctement qu'à une base de données qui contient toujours l'état de base. Pour une base de données qui contient déjà l'un des côtés, appliquez une sélection des opérations qui lui manquent ; voir Application protégée.
$applied = Merql::apply($result);
echo $applied->rowsAffected();
plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan
Créez un plan de fusion compatible avec UI à partir de trois instantanés nommés. Le plan comprend des ID d'opération stables, des ID de groupes de modifications, le contexte des opérations, les conflits, les différences de schéma, un résumé, des métadonnées et un hachage du plan. La fusion s'effectue au niveau des colonnes, comme dans merge() ; transmettez un résultat fusionné au niveau des cellules à MergePlanBuilder::build() pour effectuer une fusion à ce niveau.
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
'source' => 'review-ui',
]);
applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult
Appliquez un résultat de fusion sans conflit avec des préconditions optimistes sur les lignes en temps réel. Pour les mises à jour et les suppressions, les lignes actives doivent correspondre à $expectedLive ; pour les insertions, l'identité cible ne doit pas déjà exister. $expectedLive est un instantané de la base de données cible (theirs, lorsque ours est promu dans la base à partir de laquelle theirs a été créé). Toute instruction qui ne modifie aucune ligne annule la transaction.
use Merql\Snapshot\SnapshotStore;
$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
reset(): void
Efface les instances internes de connexion et de Snapshotter. Sert principalement à réinitialiser l'état entre les exécutions de tests.
Connexion
Merql\Connection construit des adaptateurs de connexion avec des valeurs par défaut adaptées. Les connexions PDO utilisent les exceptions en cas d'erreur, le mode de récupération associatif, les préparations non émulées et la récupération des valeurs sous forme de chaînes. L'adaptateur mysqli normalise les valeurs récupérées pour obtenir la même forme chaîne ou null.
mysql(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): PdoDatabaseConnection
use Merql\Connection;
$connection = Connection::mysql('127.0.0.1', 'my_app', 'root', 'secret');
mysqli(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): MysqliDatabaseConnection
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');
sqlite(string $path = ':memory:'): PdoDatabaseConnection
Crée une connexion SQLite. Active automatiquement les clés étrangères via PRAGMA foreign_keys = ON.
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection
Connectez-vous à l'aide de n'importe quelle chaîne DSN PDO. merql fournit des pilotes uniquement pour mysql et sqlite ; les autres DSN permettent d'ouvrir une connexion, mais Merql::init(), Snapshotter et les composants d'application lèvent Unsupported database driver, sauf si un pilote est enregistré avec DriverFactory::register().
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
fromPdo(PDO $pdo): PdoDatabaseConnection
Encapsule une instance PDO existante.
fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection
Encapsule une instance mysqli existante.
Contrat des adaptateurs
Tous les adaptateurs implémentent Merql\Database\DatabaseConnection :
interface DatabaseConnection
{
public function driverName(): string;
public function execute(string $sql, array $params = []): int;
public function query(string $sql, array $params = []): array;
public function scalar(string $sql, array $params = []): ?string;
public function beginTransaction(): void;
public function commit(): void;
public function rollBack(): void;
public function lastInsertId(): string;
}
execute(), query() et scalar() acceptent des paramètres positionnels list<scalar|null>
pour les espaces réservés ?. query() renvoie
list<array<string, scalar|null>> ; les valeurs sont normalisées sous la même forme chaîne ou null
avec les adaptateurs PDO et mysqli.
Snapshotter
Merql\Snapshot\Snapshotter capture l'état de la base de données. Contrairement à la façade, il vous donne un contrôle total sur les filtres et n'enregistre pas automatiquement les instantanés.
__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)
Si aucun pilote n'est fourni, il est détecté automatiquement à partir de l'identité de la connexion. Fournissez un
IdentityRuleSet si une identité de ligne propre à une table doit remplacer les
valeurs par défaut du schéma.
capture(string $name, array $tables = [], array $filters = []): Snapshot
Capturez l'état actuel. Indiquez des noms de tables pour limiter la portée. Passez des objets de filtre pour exclure des tables, des colonnes ou des lignes.
use Merql\Snapshot\Snapshotter;
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Filter\RowFilter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
$snapshotter = new Snapshotter(
$connection,
identityRules: new IdentityRuleSet([
'wp_options' => IdentityRule::natural(['option_name']),
]),
);
// All tables.
$snapshot = $snapshotter->capture('full');
// Specific tables with filters.
$snapshot = $snapshotter->capture('filtered', ['posts', 'users'], [
TableFilter::exclude(['cache_*', 'sessions']),
ColumnFilter::ignore(['updated_at', 'created_at']),
RowFilter::create(fn(string $table, array $row) => $row['status'] !== 'trash'),
]);
captureAliased(string $name, array $physicalToCanonicalTableMap, array $filters = []): Snapshot
Capturez les tables physiques en leur attribuant des noms de table canoniques. Utilisez cette méthode lorsque la même table logique porte des noms physiques différents selon les environnements.
$snapshot = $snapshotter->captureAliased('sandbox', [
'wp_onumia_abcd_posts' => 'wp_posts',
'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
L'instantané obtenu enregistre les noms de tables canoniques tout en conservant le schéma des tables physiques, les colonnes d'identité configurées, les clés des lignes et les filtres.
fromData(string $name, array $tableData): Snapshot (statique)
Créez un instantané à partir de données brutes, sans connexion à une base de données. Utile pour les tests ou l'importation de données depuis une autre source.
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;
$schema = new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']);
$snapshot = Snapshotter::fromData('test', [
'posts' => new TableSnapshotData($schema, [
['id' => '1', 'title' => 'Hello'],
['id' => '2', 'title' => 'World'],
], ['id']),
]);
Instantané
Merql\Snapshot\Snapshot est un objet valeur en lecture seule qui représente un état complet de base de données.
$snapshot->name; // string: snapshot identifier
$snapshot->tables; // array<string, TableSnapshot>
$snapshot->tableNames(); // list<string>
$snapshot->hasTable('posts'); // bool
$snapshot->getTable('posts'); // ?TableSnapshot
TableSnapshot
Merql\Snapshot\TableSnapshot contient les lignes, les empreintes numériques et le schéma d'une table.
$table = $snapshot->getTable('posts');
$table->schema; // TableSchema
$table->identityColumns; // list<string>: columns used for row identity
$table->fingerprints; // array<string, string>: row key to hash
$table->rows; // array<string, array<string, mixed>>
$table->rowKeys(); // list<string>
$table->rowCount(); // int
$table->hasRow('42'); // bool
$table->getRow('42'); // ?array<string, mixed>
$table->getFingerprint('42'); // ?string
TableSnapshotData
Merql\Snapshot\TableSnapshotData représente les données d'entrée de Snapshotter::fromData().
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;
$data = new TableSnapshotData(
schema: new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']),
rows: [
['id' => '1', 'title' => 'First Post'],
],
identityColumns: ['id'],
);
IdentityRule
Merql\Identity\IdentityRule définit les colonnes utilisées pour construire les clés des lignes.
use Merql\Identity\IdentityRule;
$primary = IdentityRule::primary(['id']);
$natural = IdentityRule::natural(['option_name']);
$composite = IdentityRule::composite(['object_id', 'term_taxonomy_id']);
$content = IdentityRule::content(['name', 'value']);
$key = $natural->key(['option_name' => 'siteurl']);
// "siteurl"
forSchema(TableSchema $schema): IdentityRule (statique)
Résout la règle d'identité par défaut à partir du schéma : clé primaire, première clé unique, puis repli sur le contenu.
toArray(): array
Sérialise la règle en ['type' => string, 'columns' => list<string>].
fromArray(array $data): IdentityRule (statique)
Hydrate une règle à partir de données sérialisées.
IdentityRuleSet
Merql\Identity\IdentityRuleSet stocke les remplacements de règles d'identité propres aux tables.
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
$rules = new IdentityRuleSet([
'wp_options' => IdentityRule::natural(['option_name']),
]);
$rule = $rules->ruleFor($schema);
with(string $table, IdentityRule $rule): IdentityRuleSet
Renvoie un nouvel ensemble de règles auquel une substitution pour une table a été ajoutée.
conflictsFor(string $table, IdentityRule $rule, array $rows): array
Renvoie les entrées IdentityConflict lorsque plusieurs lignes produisent la même clé pour une
règle.
toArray(): array
Sérialise les règles de table sous forme de tableau compatible avec JSON.
fromArray(array $data): IdentityRuleSet (statique)
Hydrate un ensemble de règles à partir de données sérialisées.
SnapshotStore
Merql\Snapshot\SnapshotStore enregistre et charge les instantanés sous forme de fichiers JSON.
setDirectory(string $directory): void
Définit le répertoire de stockage. La valeur par défaut est .merql/snapshots.
save(Snapshot $snapshot): void
Écrit un instantané sur le disque.
load(string $name): Snapshot
Charge un instantané par nom. Lève SnapshotException s'il est introuvable.
exists(string $name): bool
Vérifie si un fichier d'instantané existe.
delete(string $name): void
Supprime un fichier d'instantané.
Comparateur
Merql\Diff\Differ compare deux instantanés et produit un ensemble de modifications.
diff(Snapshot $base, Snapshot $current): Changeset
use Merql\Diff\Differ;
$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
valuesEqual(mixed $a, mixed $b): bool (statique)
Compare deux valeurs après coercition en chaînes. NULL est traité comme une valeur distincte : NULL === NULL est vrai, NULL === '' est faux.
Ensemble de modifications
Merql\Diff\Changeset est une collection en lecture seule d'opérations au niveau des lignes.
$changeset->inserts(); // list<RowInsert>
$changeset->updates(); // list<RowUpdate>
$changeset->deletes(); // list<RowDelete>
$changeset->isEmpty(); // bool
$changeset->count(); // int: total operations
$changeset->forTable('posts'); // Changeset: filtered to one table
RowInsert, RowUpdate, RowDelete
Objets valeur en lecture seule représentant des modifications individuelles de lignes.
RowInsert
$insert->table; // string
$insert->rowKey; // string
$insert->values; // array<string, mixed>
RowUpdate
$update->table; // string
$update->rowKey; // string
$update->columnDiffs; // list<ColumnDiff>
$update->fullRow; // array<string, mixed>: complete current row
$update->columnDiffMap(); // array<string, ColumnDiff>: keyed by column name
RowDelete
$delete->table; // string
$delete->rowKey; // string
$delete->oldValues; // array<string, mixed>: values before deletion
ColumnDiff
Merql\Diff\ColumnDiff représente une modification de colonne unique au sein d'une mise à jour de ligne.
$diff->column; // string
$diff->oldValue; // mixed
$diff->newValue; // mixed
ThreeWayMerge
Merql\Merge\ThreeWayMerge est l'algorithme central de fusion.
__construct(?CellMergeConfig $cellMergeConfig = null)
Fournissez un CellMergeConfig pour activer la fusion au niveau des cellules pour les colonnes TEXT et JSON.
use Merql\Merge\ThreeWayMerge;
use Merql\CellMerge\CellMergeConfig;
// Default: column-level merge only.
$merge = new ThreeWayMerge();
// With cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
merge(Snapshot $base, Snapshot $ours, Snapshot $theirs): MergeResult
Effectue une fusion à trois voies. Calcule les ensembles de modifications en interne (base vers ours, base vers theirs), puis les fusionne.
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
patch(Snapshot $base, Snapshot $changes): MergeResult
Raccourci pour une fusion à deux voies. Équivalent à merge($base, $base, $changes).
schemaMismatches(): array
Renvoie SchemaException[] pour toute différence de schéma détectée lors de la dernière fusion (colonnes ajoutées ou supprimées entre les instantanés).
MergeResult
Merql\Merge\MergeResult est un objet valeur en lecture seule contenant le résultat de la fusion.
$result->isClean(); // bool: no conflicts
$result->operations(); // list<MergeOperation>
$result->conflicts(); // list<Conflict>
$result->operationCount(); // int
$result->conflictCount(); // int
$result->baseSnapshot(); // ?Snapshot
$result->schemaMismatches(); // list<SchemaException>
$result->hasSchemaMismatches(); // bool
MergePlan
Merql\Plan\MergePlan est un modèle sérialisable permettant d'examiner une fusion.
$plan->id; // string
$plan->baseSnapshot; // string
$plan->oursSnapshot; // string
$plan->theirsSnapshot; // string
$plan->summary; // MergePlanSummary
$plan->changeGroups; // list<MergePlanChangeGroup>
$plan->operations; // list<MergePlanOperation>
$plan->conflicts; // list<MergePlanConflict>
$plan->schemaMismatches; // list<string>
$plan->hash; // string
$plan->metadata; // array<string, mixed>
Utilisez MergePlanSerializer::toJson() et ::fromJson() pour des allers-retours JSON stables.
Sélections
ChangeGroupSelection stocke les ID des groupes de modifications préparés. Convertissez-le en
OperationSelection lors de la création de résultats de fusion sélectionnés ou de plans de restauration.
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
$selection = ChangeGroupSelection::fromIds([$groupId]);
$operationSelection = $selection->toOperationSelection($plan);
$selected = (new SelectedMergeResultFactory())
->fromOperationSelection($plan, $operationSelection);
RollbackPlan
Merql\Rollback\RollbackPlan stocke les opérations inverses et les attentes de dérive pour une application sélectionnée.
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;
$rollback = (new RollbackPlanBuilder())->build(
'rollback-1',
$plan,
$operationSelection,
$liveBeforeRows,
);
$json = RollbackPlanSerializer::toJson($rollback);
Utilisez RollbackDrift avant d'appliquer le retour arrière, puis RollbackPlanApplier pour exécuter les opérations inverses.
MergeOperation
Merql\Merge\MergeOperation est une opération unique résolue issue de la fusion.
$op->type; // string: 'insert' | 'update' | 'delete'
$op->table; // string
$op->rowKey; // string
$op->values; // array<string, mixed>
$op->source; // string: 'ours' | 'theirs' | 'merged'
Constantes de type :
MergeOperation::TYPE_INSERT; // 'insert'
MergeOperation::TYPE_UPDATE; // 'update'
MergeOperation::TYPE_DELETE; // 'delete'
Conflit
Merql\Merge\Conflict représente un conflit de fusion non résolu.
$conflict->table(); // string
$conflict->rowKey(); // string
$conflict->type(); // string: 'update_update' | 'update_delete' | 'delete_update' | 'insert_insert'
$conflict->column(); // ?string (null for structural conflicts)
$conflict->oursValue(); // mixed
$conflict->theirsValue(); // mixed
$conflict->baseValue(); // mixed
$conflict->primaryKey(['id']); // array<string, string>: decoded from rowKey
Types de conflit
| Type | Signification |
|---|---|
update_update |
Les deux côtés ont modifié la même colonne avec des valeurs différentes |
update_delete |
Notre côté a mis à jour une ligne que leur côté a supprimée |
delete_update |
Notre côté a supprimé une ligne que leur côté a mise à jour |
insert_insert |
Les deux côtés ont inséré une ligne avec la même clé primaire |
ConflictResolver
Merql\Merge\ConflictResolver applique une règle de résolution à tous les conflits d'un résultat de fusion.
resolve(MergeResult $result, ConflictPolicy $policy): MergeResult (statique)
Renvoie un nouveau MergeResult dont les conflits sont résolus selon la règle.
use Merql\Merge\ConflictResolver;
use Merql\Merge\ConflictPolicy;
// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);
// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);
// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
ConflictPolicy
Merql\Merge\ConflictPolicy est une énumération adossée comportant trois cas.
use Merql\Merge\ConflictPolicy;
ConflictPolicy::OursWins; // Resolve all conflicts in favor of "ours"
ConflictPolicy::TheirsWins; // Resolve all conflicts in favor of "theirs"
ConflictPolicy::Manual; // Leave conflicts unresolved for manual handling
ColumnMerge
Merql\Merge\ColumnMerge applique la logique de fusion colonne par colonne pour une seule ligne. Utilisé en interne par ThreeWayMerge.
merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array (statique)
Renvoie ['values' => array, 'conflicts' => list<Conflict>].
use Merql\Merge\ColumnMerge;
$result = ColumnMerge::merge(
'posts',
'42',
['title' => 'Hello', 'status' => 'draft'], // base
['title' => 'Hello', 'status' => 'published'], // ours
['title' => 'New Title', 'status' => 'draft'], // theirs
);
// $result['values'] = ['title' => 'New Title', 'status' => 'published']
// $result['conflicts'] = [] (clean merge)
Appliqueur
Merql\Apply\Applier exécute un résultat de fusion sur une base de données.
__construct(DatabaseConnection $connection, ?Driver $driver = null)
apply(MergeResult $result, ?Snapshot $base = null): ApplyResult
Exécute toutes les opérations dans une seule transaction. Annule la transaction en cas d'erreur et la signale dans ApplyResult::errors() au lieu de lever une exception. Lève ConflictException si le résultat comporte des conflits non résolus.
use Merql\Apply\Applier;
$applier = new Applier($connection);
$applied = $applier->apply($result);
echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
print_r($applied->errors());
}
L'ordre des clés étrangères est géré automatiquement : les insertions sont triées selon l'ordre des dépendances, et les suppressions dans l'ordre inverse.
ApplyResult
Merql\Apply\ApplyResult est un objet valeur en lecture seule renvoyé par l'appliqueur.
$applied->rowsAffected(); // int
$applied->errors(); // list<string>
$applied->hasErrors(); // bool
Après une application annulée, rowsAffected() compte toujours les instructions exécutées avant l'échec (#4). Vérifiez hasErrors() avant de vous fier à ce décompte.
DryRun
Merql\Apply\DryRun génère des SQL lisibles sans rien exécuter.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (statique)
Renvoie une liste de chaînes SQL avec les valeurs insérées (à des fins d'affichage uniquement).
use Merql\Apply\DryRun;
$statements = DryRun::generate($result);
foreach ($statements as $sql) {
echo "{$sql};\n";
}
// Output:
// INSERT INTO `posts` (`id`, `title`) VALUES ('10', 'New Post');
// UPDATE `posts` SET `title` = 'Updated' WHERE `id` = '5';
// DELETE FROM `comments` WHERE `id` = '3';
SqlGenerator
Merql\Apply\SqlGenerator génère des instructions SQL paramétrées. Utilisé en interne par Applier et DryRun.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (statique)
Renvoie list<array{sql: string, params: list<scalar|null>}>.
use Merql\Apply\SqlGenerator;
$statements = SqlGenerator::generate($result);
foreach ($statements as $stmt) {
$connection->execute($stmt['sql'], $stmt['params']);
}
CellMergeConfig
Merql\CellMerge\CellMergeConfig configure des stratégies de fusion au niveau des cellules pour des colonnes ou types de colonnes spécifiques.
auto(): self (statique)
Fabrique pratique qui attribue TextCellMerger aux colonnes TEXT/LONGTEXT et JsonCellMerger aux colonnes JSON.
use Merql\CellMerge\CellMergeConfig;
$config = CellMergeConfig::auto();
forColumn(string $column, CellMerger $merger): self
Attribue un fusionneur à une colonne spécifique. Utilisez table.column pour les noms qualifiés ou simplement column pour toutes les tables.
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\TextCellMerger;
$config = (new CellMergeConfig())
->forColumn('posts.content', new TextCellMerger())
->forColumn('description', new TextCellMerger());
forType(string $typePattern, CellMerger $merger): self
Attribue un fusionneur à toutes les colonnes correspondant à un modèle de type (correspondance de sous-chaîne sans distinction de casse).
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
$config = (new CellMergeConfig())
->forType('json', new JsonCellMerger());
getMerger(string $table, string $column, string $columnType = ''): CellMerger
Recherche le fusionneur pour une table, une colonne et un type donnés. Priorité : nom de colonne qualifié, nom de colonne non qualifié, modèle de type, valeur par défaut (opaque).
CellMerger (interface)
Merql\CellMerge\CellMerger est l'interface des stratégies de fusion au niveau des cellules.
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;
interface CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
Trois implémentations intégrées sont fournies.
TextCellMerger
Merql\CellMerge\TextCellMerger fusionne le texte multiligne à l’aide d’une comparaison ligne par ligne à trois voies (le même algorithme que git utilise pour le contenu des fichiers). Lorsque les deux côtés modifient des lignes différentes, la fusion est propre. Lorsqu’ils modifient différemment la même ligne, un conflit est signalé.
use Merql\CellMerge\TextCellMerger;
$merger = new TextCellMerger();
$result = $merger->merge(
"line 1\nline 2\nline 3",
"line 1\nline 2 modified\nline 3",
"line 1\nline 2\nline 3 modified",
);
// $result->clean === true
// $result->value === "line 1\nline 2 modified\nline 3 modified"
JsonCellMerger
Merql\CellMerge\JsonCellMerger fusionne les valeurs JSON en comparant indépendamment les clés de premier niveau. Chaque clé est fusionnée selon une logique à trois voies. Les objets imbriqués sont comparés comme des chaînes JSON opaques (sans fusion récursive).
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
$result = $merger->merge(
'{"a": 1, "b": 2}',
'{"a": 10, "b": 2}', // changed "a"
'{"a": 1, "b": 20}', // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
OpaqueCellMerger
Merql\CellMerge\OpaqueCellMerger est la valeur par défaut. Il traite les valeurs comme des chaînes opaques et signale toujours un conflit lorsqu'elles diffèrent.
CellMergeResult
Merql\CellMerge\CellMergeResult est le type de retour de tous les fusionneurs de cellules.
$result->clean; // bool: true if merge succeeded
$result->value; // mixed: merged value (or ours on conflict)
$result->conflicts; // int: number of conflicts
// Factory methods.
CellMergeResult::resolved($value); // clean merge
CellMergeResult::conflict($oursValue, $n); // conflict with count
TableSchema
Merql\Schema\TableSchema est une représentation en lecture seule de la structure d'une table.
use Merql\Schema\TableSchema;
$schema = new TableSchema(
name: 'posts',
columns: ['id' => 'int', 'title' => 'varchar(255)', 'content' => 'text'],
primaryKey: ['id'],
uniqueKeys: [['slug']],
);
$schema->name; // string
$schema->columns; // array<string, string>: name to type
$schema->primaryKey; // list<string>
$schema->uniqueKeys; // list<list<string>>
$schema->hasPrimaryKey(); // bool
$schema->hasUniqueKeys(); // bool
$schema->columnNames(); // list<string>
Système de pilotes
merql utilise un système de pilotes extensible pour abstraire les opérations propres à chaque base de données. Les pilotes sont détectés automatiquement à partir de l'identité du pilote de connexion.
Driver (interface)
Merql\Driver\Driver définit le contrat mis en œuvre par tous les pilotes de base de données.
interface Driver
{
public function quoteIdentifier(string $name): string;
public function listTables(DatabaseConnection $connection): array;
public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
public function readForeignKeys(DatabaseConnection $connection): array;
public function selectAll(string $table): string;
}
Implémentations intégrées : MysqlDriver et SqliteDriver.
DriverFactory
Merql\Driver\DriverFactory détecte automatiquement le pilote à partir d'une connexion.
use Merql\Driver\DriverFactory;
$driver = DriverFactory::create($connection);
Enregistrez un pilote personnalisé pour d'autres bases de données :
DriverFactory::register('pgsql', MyPostgresDriver::class);
Filtres
TableFilter
Merql\Filter\TableFilter inclut ou exclut des tables à l'aide de modèles glob.
use Merql\Filter\TableFilter;
$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
ColumnFilter
Merql\Filter\ColumnFilter retire des instantanés des colonnes spécifiques.
use Merql\Filter\ColumnFilter;
$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
RowFilter
Merql\Filter\RowFilter filtre les lignes à l'aide d'une fonction prédicat.
use Merql\Filter\RowFilter;
$filter = RowFilter::create(function (string $table, array $row): bool {
// Exclude trashed posts.
if ($table === 'posts' && ($row['status'] ?? '') === 'trash') {
return false;
}
return true;
});
Exceptions
SnapshotException
Merql\Exceptions\SnapshotException est levée lorsqu'un instantané est introuvable ou porte un nom invalide.
SnapshotException::notFound('baseline', '/path/to/baseline.json');
MergeException
Merql\Exceptions\MergeException est levée lorsqu'un instantané requis pour une fusion est manquant.
MergeException::snapshotRequired('base');
ConflictException
Merql\Exceptions\ConflictException est levée lorsqu'on tente d'appliquer un résultat de fusion comportant des conflits non résolus.
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
SchemaException
Merql\Exceptions\SchemaException est levée (ou recueillie) lorsque les schémas diffèrent entre les instantanés.
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
Les divergences de schéma n'interrompent pas la fusion. Elles sont recueillies et consultables via $result->schemaMismatches().