Referencia de PHP API
Referencia completa de todas las clases y métodos públicos de merql.
Merql (fachada)
Merql\Merql es el punto de entrada estático para las operaciones comunes. Envuelve las clases de nivel inferior y gestiona automáticamente la persistencia de las instantáneas.
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
Inicializa la fachada con una conexión a la base de datos. Debe llamarse antes de
snapshot(), apply() o applyGuarded(); vuelve a llamarla para cambiar de base de datos,
por ejemplo, para crear instantáneas de ours y theirs desde dos copias distintas. Si no se proporciona
$driver, llama a DriverFactory::create(), que genera una excepción para motores distintos de
MySQL y SQLite, salvo que se registre un controlador. Pasa un controlador para anular la detección automática, o un conjunto de reglas de identidad para anular la identidad de filas por tabla durante la captura de instantáneas.
snapshot(string $name, array $tables = []): Snapshot
Captura el estado actual de la base de datos y lo guarda en disco. Pasa una matriz de nombres de tablas para crear instantáneas de tablas concretas, o no pases nada para incluirlas todas.
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
diff(string $base, string $current): Changeset
Carga dos instantáneas con nombre y calcula las diferencias entre ellas. No requiere init() porque trabaja con instantáneas guardadas.
$changeset = Merql::diff('before', 'after');
merge(string $base, string $ours, string $theirs): MergeResult
Fusión de tres vías de tres instantáneas con nombre. Devuelve un resultado con operaciones sin conflictos y los conflictos que haya. Fusiona a nivel de columna: la fachada construye ThreeWayMerge sin CellMergeConfig, por lo que no se aplica la fusión a nivel de celda. Para ello, usa directamente ThreeWayMerge.
$result = Merql::merge('base', 'ours', 'theirs');
patch(string $base, string $changes): MergeResult
Fusión de dos vías: aplica los cambios sobre la base. Equivale a merge($base, $base, $changes). Siempre genera un resultado sin conflictos porque no hay ediciones simultáneas. Solo funciona a nivel de columna, como merge().
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
Ejecuta un resultado de fusión sin conflictos en la base de datos pasada a init(). Requiere init(). Genera ConflictException si quedan conflictos sin resolver.
Se ejecutan todas las operaciones del resultado, así que el resultado completo de merge() solo se aplica sin problemas a una base de datos que aún conserva el estado base. Para una base de datos que ya contiene uno de los lados, aplica una selección de las operaciones que le faltan; consulta Aplicación protegida.
$applied = Merql::apply($result);
echo $applied->rowsAffected();
plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan
Crea un plan de fusión listo para UI a partir de tres instantáneas con nombre. El plan incluye identificadores estables de operación, identificadores de grupos de cambios, contexto de las operaciones, conflictos, discrepancias del esquema, un resumen, metadatos y un hash del plan. La fusión es a nivel de columna, como en merge(); pasa un resultado fusionado a nivel de celda a MergePlanBuilder::build() para una fusión a nivel de celda.
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
'source' => 'review-ui',
]);
applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult
Aplica un resultado de fusión sin conflictos con condiciones previas optimistas para las filas activas. Las actualizaciones y eliminaciones requieren que las filas activas actuales coincidan con $expectedLive; las inserciones requieren que la identidad de destino aún no exista. $expectedLive es una instantánea de la base de datos de destino (theirs, cuando se promociona ours a la base de datos de la que se tomó theirs). Cualquier instrucción que no afecte ninguna fila revierte la transacción.
use Merql\Snapshot\SnapshotStore;
$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
reset(): void
Borra las instancias internas de conexión y Snapshotter. Se usa principalmente en pruebas para restablecer el estado entre ejecuciones.
Conexión
Merql\Connection crea adaptadores de conexión con valores predeterminados razonables. Las conexiones PDO usan excepciones en caso de error, modo de captura asociativa, consultas preparadas no emuladas y valores capturados convertidos a cadenas. El adaptador mysqli normaliza los valores capturados al mismo formato de cadena o nulo.
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
Crea una conexión SQLite. Habilita automáticamente las claves externas mediante PRAGMA foreign_keys = ON.
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection
Conéctate usando cualquier cadena DSN de PDO. merql incluye controladores solo para mysql y sqlite; otros DSN abren una conexión, pero Merql::init(), Snapshotter y los aplicadores generan Unsupported database driver salvo que se registre un controlador con DriverFactory::register().
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
fromPdo(PDO $pdo): PdoDatabaseConnection
Envuelve una instancia PDO existente.
fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection
Envuelve una instancia mysqli existente.
Contrato del adaptador
Todos los adaptadores implementan 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() y scalar() aceptan parámetros posicionales list<scalar|null>
para los marcadores de posición ?. query() devuelve
list<array<string, scalar|null>>; los valores se normalizan al mismo formato de cadena o nulo en los adaptadores PDO y mysqli.
Snapshotter
Merql\Snapshot\Snapshotter captura el estado de la base de datos. A diferencia de la fachada, te da control total sobre los filtros y no guarda automáticamente las instantáneas.
__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)
El controlador se detecta automáticamente a partir de la identidad de la conexión si no se proporciona. Pasa un
IdentityRuleSet cuando la identidad de fila específica de una tabla debe anular los valores predeterminados del esquema.
capture(string $name, array $tables = [], array $filters = []): Snapshot
Captura el estado actual. Pasa nombres de tablas para limitar el alcance. Pasa objetos de filtro para excluir tablas, columnas o filas.
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
Captura tablas físicas con nombres de tabla canónicos. Usa esta opción cuando una misma tabla lógica tenga nombres físicos distintos entre entornos.
$snapshot = $snapshotter->captureAliased('sandbox', [
'wp_onumia_abcd_posts' => 'wp_posts',
'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
La instantánea resultante guarda los nombres canónicos de las tablas y conserva el esquema físico de la tabla, las columnas de identidad configuradas, las claves de fila y los filtros.
fromData(string $name, array $tableData): Snapshot (estático)
Crea una instantánea a partir de datos sin procesar, sin conexión a la base de datos. Es útil para probar o importar datos de otra fuente.
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']),
]);
Snapshot
Merql\Snapshot\Snapshot es un objeto de valor de solo lectura que representa un estado completo de la base de datos.
$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 contiene las filas, las huellas digitales y el esquema de una tabla.
$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 son los datos de entrada para 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 define las columnas utilizadas para crear claves de fila.
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 (estático)
Resuelve la regla de identidad predeterminada a partir del esquema: clave principal, primera clave única y, por último, alternativa basada en el contenido.
toArray(): array
Serializa la regla como ['type' => string, 'columns' => list<string>].
fromArray(array $data): IdentityRule (estático)
Restaura una regla a partir de datos serializados.
IdentityRuleSet
Merql\Identity\IdentityRuleSet guarda anulaciones de identidad específicas de cada tabla.
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
Devuelve un conjunto de reglas nuevo con una anulación para una tabla.
conflictsFor(string $table, IdentityRule $rule, array $rows): array
Devuelve las entradas de IdentityConflict cuando varias filas generan la misma clave para una regla.
toArray(): array
Serializa las reglas de las tablas como una matriz segura para JSON.
fromArray(array $data): IdentityRuleSet (estático)
Restaura un conjunto de reglas a partir de datos serializados.
SnapshotStore
Merql\Snapshot\SnapshotStore guarda y carga instantáneas como archivos JSON.
setDirectory(string $directory): void
Establece el directorio de almacenamiento. El valor predeterminado es .merql/snapshots.
save(Snapshot $snapshot): void
Escribe una instantánea en disco.
load(string $name): Snapshot
Carga una instantánea por nombre. Genera SnapshotException si no se encuentra.
exists(string $name): bool
Comprueba si existe un archivo de instantánea.
delete(string $name): void
Elimina un archivo de instantánea.
Differ
Merql\Diff\Differ compara dos instantáneas y genera un conjunto de cambios.
diff(Snapshot $base, Snapshot $current): Changeset
use Merql\Diff\Differ;
$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
valuesEqual(mixed $a, mixed $b): bool (estático)
Compara dos valores convirtiéndolos a cadenas. NULL se trata como un valor distinto: NULL === NULL es verdadero, NULL === '' es falso.
Changeset
Merql\Diff\Changeset es una colección de solo lectura de operaciones a nivel de fila.
$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
Objetos de valor de solo lectura que representan cambios individuales en filas.
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 representa un cambio de columna individual dentro de una actualización de fila.
$diff->column; // string
$diff->oldValue; // mixed
$diff->newValue; // mixed
ThreeWayMerge
Merql\Merge\ThreeWayMerge es el algoritmo central de fusión.
__construct(?CellMergeConfig $cellMergeConfig = null)
Pasa un CellMergeConfig para habilitar la fusión a nivel de celda en columnas TEXT y 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
Realiza una fusión de tres vías. Calcula internamente conjuntos de cambios (de base a ours y de base a theirs) y los fusiona.
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
patch(Snapshot $base, Snapshot $changes): MergeResult
Atajo para la fusión de dos vías. Equivale a merge($base, $base, $changes).
schemaMismatches(): array
Devuelve SchemaException[] por cada diferencia de esquema detectada durante la última fusión (columnas añadidas o eliminadas entre instantáneas).
MergeResult
Merql\Merge\MergeResult es un objeto de valor de solo lectura que contiene el resultado de la fusión.
$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 es un modelo serializable para revisar una fusión.
$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>
Usa MergePlanSerializer::toJson() y ::fromJson() para realizar viajes de ida y vuelta estables de JSON.
Selecciones
ChangeGroupSelection guarda los ID de los grupos de cambios preparados. Conviértelo en un OperationSelection al crear resultados de fusión seleccionados o planes de reversión.
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 guarda operaciones inversas y expectativas de desviación para una aplicación seleccionada.
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;
$rollback = (new RollbackPlanBuilder())->build(
'rollback-1',
$plan,
$operationSelection,
$liveBeforeRows,
);
$json = RollbackPlanSerializer::toJson($rollback);
Usa RollbackDrift antes de aplicar la reversión y, luego, RollbackPlanApplier para ejecutar las operaciones inversas.
MergeOperation
Merql\Merge\MergeOperation es una única operación resuelta de la fusión.
$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 tipo:
MergeOperation::TYPE_INSERT; // 'insert'
MergeOperation::TYPE_UPDATE; // 'update'
MergeOperation::TYPE_DELETE; // 'delete'
Conflicto
Merql\Merge\Conflict representa un conflicto de fusión sin resolver.
$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
Tipos de conflicto
| Tipo | Significado |
|---|---|
update_update |
Ambos lados cambiaron la misma columna a valores distintos |
update_delete |
Ours actualizó una fila que theirs eliminó |
delete_update |
Ours eliminó una fila que theirs actualizó |
insert_insert |
Ambos lados insertaron una fila con la misma clave principal |
ConflictResolver
Merql\Merge\ConflictResolver aplica una política de resolución a todos los conflictos de un resultado de fusión.
resolve(MergeResult $result, ConflictPolicy $policy): MergeResult (estático)
Devuelve un nuevo MergeResult con los conflictos resueltos según la política.
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 es un enum respaldado con tres casos.
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 realiza la lógica de combinación por columna para una sola fila. ThreeWayMerge lo usa internamente.
merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array (estático)
Devuelve ['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)
Aplicador
Merql\Apply\Applier ejecuta un resultado de combinación en una base de datos.
__construct(DatabaseConnection $connection, ?Driver $driver = null)
apply(MergeResult $result, ?Snapshot $base = null): ApplyResult
Ejecuta todas las operaciones en una sola transacción. Revierte la transacción ante cualquier error y lo informa en ApplyResult::errors() en lugar de lanzarlo. Lanza ConflictException si el resultado tiene conflictos sin resolver.
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());
}
El orden de las claves externas se gestiona automáticamente: las inserciones se ordenan según las dependencias y las eliminaciones en orden inverso.
ApplyResult
Merql\Apply\ApplyResult es un objeto de valor de solo lectura que devuelve el aplicador.
$applied->rowsAffected(); // int
$applied->errors(); // list<string>
$applied->hasErrors(); // bool
Tras revertir una aplicación, rowsAffected() sigue contando las instrucciones ejecutadas antes del error (#4). Compruebe hasErrors() antes de confiar en el recuento.
DryRun
Merql\Apply\DryRun genera instrucciones SQL legibles sin ejecutar nada.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (estático)
Devuelve una lista de cadenas SQL con los valores insertados (solo para mostrarlos).
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 genera instrucciones SQL parametrizadas. Applier y DryRun las usan internamente.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (estático)
Devuelve 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 configura estrategias de combinación a nivel de celda para columnas o tipos de columna específicos.
auto(): self (estático)
Fábrica de conveniencia que asigna TextCellMerger a las columnas TEXT/LONGTEXT y JsonCellMerger a las columnas JSON.
use Merql\CellMerge\CellMergeConfig;
$config = CellMergeConfig::auto();
forColumn(string $column, CellMerger $merger): self
Asigne un combinador a una columna específica. Use table.column para nombres cualificados o solo column para todas las tablas.
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
Asigne un combinador a todas las columnas que coincidan con un patrón de tipo (coincidencia de subcadena sin distinguir mayúsculas y minúsculas).
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
$config = (new CellMergeConfig())
->forType('json', new JsonCellMerger());
getMerger(string $table, string $column, string $columnType = ''): CellMerger
Busca el combinador para una tabla, columna y tipo determinados. Prioridad: nombre de columna cualificado, nombre de columna no cualificado, patrón de tipo, predeterminado (opaco).
CellMerger (interfaz)
Merql\CellMerge\CellMerger es la interfaz para las estrategias de combinación a nivel de celda.
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;
interface CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
Se proporcionan tres implementaciones integradas.
TextCellMerger
Merql\CellMerge\TextCellMerger combina texto multilínea mediante una comparación de diferencias de tres vías, línea por línea (el mismo algoritmo que usa git para el contenido de archivos). Si ambos lados editan líneas distintas, la combinación se realiza sin conflictos. Si ambos editan la misma línea de forma diferente, informa de un conflicto.
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 combina valores JSON comparando las claves de nivel superior por separado. Cada clave se combina mediante lógica de tres vías. Los objetos anidados se comparan como cadenas JSON opacas (no se combinan de forma recursiva).
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 es el valor predeterminado. Trata los valores como cadenas opacas y siempre informa de un conflicto cuando difieren.
CellMergeResult
Merql\CellMerge\CellMergeResult es el tipo de retorno de todos los combinadores de celdas.
$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 es una representación de solo lectura de la estructura de una tabla.
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>
Sistema de controladores
merql usa un sistema de controladores conectables para abstraer las operaciones específicas de cada base de datos. Los controladores se detectan automáticamente a partir de la identidad del controlador de conexión.
Driver (interfaz)
Merql\Driver\Driver define el contrato que implementan todos los controladores de bases de datos.
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;
}
Implementaciones integradas: MysqlDriver y SqliteDriver.
DriverFactory
Merql\Driver\DriverFactory detecta automáticamente el controlador a partir de una conexión.
use Merql\Driver\DriverFactory;
$driver = DriverFactory::create($connection);
Registre un controlador personalizado para otras bases de datos:
DriverFactory::register('pgsql', MyPostgresDriver::class);
Filtros
TableFilter
Merql\Filter\TableFilter incluye o excluye tablas mediante patrones glob.
use Merql\Filter\TableFilter;
$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
ColumnFilter
Merql\Filter\ColumnFilter elimina columnas específicas de las instantáneas.
use Merql\Filter\ColumnFilter;
$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
RowFilter
Merql\Filter\RowFilter filtra filas mediante una función de predicado.
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;
});
Excepciones
SnapshotException
Merql\Exceptions\SnapshotException se lanza cuando no se encuentra una instantánea o su nombre no es válido.
SnapshotException::notFound('baseline', '/path/to/baseline.json');
MergeException
Merql\Exceptions\MergeException se lanza cuando falta una instantánea necesaria para una combinación.
MergeException::snapshotRequired('base');
ConflictException
Merql\Exceptions\ConflictException se lanza al intentar aplicar un resultado de combinación que tiene conflictos sin resolver.
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
SchemaException
Merql\Exceptions\SchemaException se lanza (o se recopila) cuando los esquemas difieren entre instantáneas.
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
Las discrepancias de esquema no detienen la combinación. Se recopilan y están disponibles en $result->schemaMismatches() para su inspección.