Planes de combinación
Un MergeResult es el resultado ejecutable de una combinación. Un MergePlan es la forma inspeccionable de ese resultado. Úselo cuando una aplicación necesite mostrar, preparar, serializar o aplicar selectivamente cambios en la base de datos antes de escribir en la base de datos de destino.
use Merql\Merql;
$plan = Merql::plan(
id: 'plan-2026-06-06',
base: 'base',
ours: 'ours',
theirs: 'theirs',
metadata: ['sandbox_id' => 'ow_123'],
);
Merql::plan() carga las tres instantáneas nombradas y las combina a nivel de columna, como Merql::merge(). Para planificar una combinación que use combinación a nivel de celda en columnas TEXT y JSON, cree el resultado usted mismo y páselo a MergePlanBuilder:
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;
$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');
$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);
El plan contiene:
- los nombres de las instantáneas usadas para crearlo,
- un resumen de operaciones, conflictos, discrepancias de esquema, tablas y grupos,
- identificadores de operación estables,
- identificadores de grupo de cambios estables,
- columnas de identidad de filas y valores de identidad,
- valores de fila de base, nuestro lado, el suyo y el resultado para cada operación,
- metadatos opcionales de la aplicación anfitriona,
- un hash del plan,
- una versión del esquema.
Identificadores estables
Los identificadores de operación son deterministas para el mismo contenido de operación. Se derivan del nombre de la tabla, la clave de fila, el tipo de operación, el origen de la operación, las columnas modificadas y los valores resultantes.
Los identificadores de grupo de cambios son deterministas para el mismo contenido del grupo. El generador predeterminado crea un grupo de operación por cada operación. Las aplicaciones anfitrionas pueden crear grupos de dominio más amplios a partir de la lista de operaciones y serializarlos con la misma estructura DTO.
foreach ($plan->changeGroups as $group) {
echo $group->id . ': ' . implode(', ', $group->operationIds);
}
Estructura de la operación
Cada MergePlanOperation conserva contexto suficiente para su revisión y reversión.
$operation = $plan->operations[0];
$operation->id; // stable operation ID
$operation->type; // insert, update, delete
$operation->table; // canonical table name
$operation->rowKey; // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues; // decoded identity values
$operation->source; // ours, theirs, or merged
$operation->baseRow; // row in base snapshot, or null
$operation->oursRow; // row in ours snapshot, or null
$operation->theirsRow; // row in theirs snapshot, or null
$operation->resultValues; // values the merge would write
$operation->changedColumns; // changed result columns
Selecciones
Use ChangeGroupSelection cuando UI prepare grupos. Conviértalo en un OperationSelection al aplicar cambios o generar artefactos de reversión.
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;
$selection = ChangeGroupSelection::fromIds([
$plan->changeGroups[0]->id,
]);
$operationSelection = $selection->toOperationSelection($plan);
$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
$plan,
$operationSelection,
SnapshotStore::load($plan->baseSnapshot),
);
El resultado seleccionado es un MergeResult limpio que solo contiene las operaciones seleccionadas. Se puede pasar a Merql::apply() o Merql::applyGuarded().
Seleccionar lo que le falta al destino
Un plan contiene las operaciones de ambas partes, porque el resultado de la combinación transforma la base en el estado combinado. Cuando el destino ya contiene los cambios de una parte, seleccione solo las operaciones que aún no tiene. Para un destino que contiene los cambios de su lado, como una base de datos live que recibe las ediciones de una copia sandbox, seleccione todas las operaciones con origen ours, más las actualizaciones merged, que combinan los cambios de columnas de ambas partes:
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;
// Rows with an unresolved conflict stay out of the selection.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
$conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}
$ids = [];
foreach ($plan->operations as $operation) {
$missing = $operation->source === 'ours'
|| ($operation->source === 'merged' && $operation->type === 'update');
if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
$ids[] = $operation->id;
}
}
$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
$plan,
OperationSelection::fromIds($ids),
SnapshotStore::load($plan->baseSnapshot),
);
Las operaciones con origen theirs y las eliminaciones merged (filas eliminadas por ambas partes) ya están presentes en ese destino.
Un plan creado a partir de una combinación con conflictos sigue enumerando una operación por cada fila en conflicto que ambas partes actualizaron. Sus valores de resultado conservan el valor de nuestro lado en la columna en conflicto, y SelectedMergeResultFactory devuelve un resultado limpio para cualquier selección. En la actualidad, conflictState de la operación y del grupo de cambios indican clean incluso en ese caso (#9), así que compruebe $plan->conflicts usted mismo como se indicó antes, o resuelva los conflictos con ConflictResolver y cree el plan a partir del resultado resuelto con MergePlanBuilder. Consulte Aplicación protegida para escribir la selección con condiciones previas sobre las filas activas.
Serialización
Los planes y las selecciones de fusión tienen una serialización JSON estable. Guarde el plan serializado si un paso de aplicación posterior debe usar exactamente el conjunto de operaciones revisado.
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\MergePlanSerializer;
$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);
$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);
Instantáneas con alias
Los planes de fusión usan nombres de tabla canónicos. Si los nombres físicos de las tablas difieren entre entornos, capture las tablas con el mismo nombre canónico antes de planificar.
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;
$snapshotter = new Snapshotter($connection);
SnapshotStore::save($snapshotter->captureAliased('live', [
'wp_posts' => 'wp_posts',
]));
SnapshotStore::save($snapshotter->captureAliased('sandbox', [
'wp_onumia_abc_posts' => 'wp_posts',
]));
El esquema almacenado en la instantánea usa el nombre canónico de la tabla y conserva las columnas, claves, columnas de identidad, claves de fila y filtros de la tabla física.