Referência de PHP API
Referência completa de todas as classes e métodos públicos em merql.
Merql (fachada)
Merql\Merql é o ponto de entrada estático para operações comuns. Ele encapsula as classes de nível inferior e gerencia automaticamente a persistência dos snapshots.
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 a fachada com uma conexão de banco de dados. Deve ser chamado antes de snapshot(), apply() ou applyGuarded(); chame-o novamente para trocar de banco, por exemplo, para criar snapshots de ours e theirs a partir de duas cópias diferentes. Sem um $driver, ele chama DriverFactory::create(), que gera uma exceção para mecanismos diferentes de MySQL e SQLite, a menos que um driver esteja registrado. Passe um driver para substituir a detecção automática, ou um conjunto de regras de identidade para substituir a identidade de linha por tabela durante a captura do snapshot.
snapshot(string $name, array $tables = []): Snapshot
Captura o estado atual do banco de dados e o salva em disco. Passe um array de nomes de tabelas para criar snapshots de tabelas específicas ou omita-o para incluir todas as tabelas.
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
diff(string $base, string $current): Changeset
Carrega dois snapshots nomeados e calcula o conjunto de alterações entre eles. Não requer init(), pois opera com snapshots persistidos.
$changeset = Merql::diff('before', 'after');
merge(string $base, string $ours, string $theirs): MergeResult
Mesclagem em três vias de três snapshots nomeados. Retorna um resultado com operações sem conflitos e eventuais conflitos. Mescla no nível de coluna: a fachada constrói ThreeWayMerge sem CellMergeConfig, portanto a mesclagem no nível da célula não é aplicada. Para isso, use diretamente ThreeWayMerge.
$result = Merql::merge('base', 'ours', 'theirs');
patch(string $base, string $changes): MergeResult
Mesclagem em duas vias: aplica alterações sobre a base. Equivale a merge($base, $base, $changes). Sempre gera um resultado sem conflitos, pois não há edições simultâneas. Somente no nível de coluna, como em merge().
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
Executa um resultado de mesclagem sem conflitos no banco de dados passado para init(). Requer init(). Gera ConflictException se ainda houver conflitos não resolvidos.
Todas as operações do resultado são executadas; por isso, o resultado completo de merge() só se aplica corretamente a um banco de dados que ainda contenha o estado base. Para um banco de dados que já contenha um dos lados, aplique uma seleção das operações que lhe faltam; consulte Aplicação protegida.
$applied = Merql::apply($result);
echo $applied->rowsAffected();
plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan
Cria um plano de mesclagem pronto para UI a partir de três snapshots nomeados. O plano inclui IDs estáveis de operação, IDs de grupos de alterações, contexto da operação, conflitos, incompatibilidades de esquema, um resumo, metadados e um hash do plano. A mesclagem ocorre no nível de coluna, como em merge(); passe um resultado mesclado por célula para MergePlanBuilder::build() para fazer a mesclagem nesse nível.
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
'source' => 'review-ui',
]);
applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult
Aplica um resultado de mesclagem sem conflitos com pré-condições otimistas nas linhas ativas. As atualizações e exclusões exigem que as linhas ativas atuais correspondam a $expectedLive; as inserções exigem que a identidade de destino ainda não exista. $expectedLive é um snapshot do banco de dados de destino (theirs, ao promover ours para o banco de dados de onde theirs foi obtido). Qualquer instrução que afete zero linhas reverte a transação.
use Merql\Snapshot\SnapshotStore;
$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
reset(): void
Limpa as instâncias internas de conexão e Snapshotter. Usado principalmente em testes para redefinir o estado entre execuções.
Conexão
Merql\Connection cria adaptadores de conexão com padrões sensatos. As conexões PDO usam exceções em caso de erro, modo de busca associativo, sem prepared statements emulados e valores buscados convertidos em strings. O adaptador mysqli normaliza os valores buscados para o mesmo formato de string 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
Cria uma conexão SQLite. Ativa automaticamente as chaves estrangeiras por meio de PRAGMA foreign_keys = ON.
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection
Conecte-se usando qualquer string DSN do PDO. merql inclui drivers apenas para mysql e sqlite; outros DSNs abrem uma conexão, mas Merql::init(), Snapshotter e os aplicadores lançam Unsupported database driver, a menos que um driver seja registrado com DriverFactory::register().
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
fromPdo(PDO $pdo): PdoDatabaseConnection
Envolve uma instância PDO existente.
fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection
Envolve uma instância mysqli existente.
Contrato do adaptador
Todos os adaptadores implementam 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() e scalar() aceitam parâmetros posicionais list<scalar|null> para placeholders ?. query() retorna list<array<string, scalar|null>>; os valores são normalizados para o mesmo formato de string ou null nos adaptadores PDO e mysqli.
Capturador de snapshots
Merql\Snapshot\Snapshotter captura o estado do banco de dados. Ao contrário da fachada, oferece controle total sobre os filtros e não persiste os dados automaticamente.
__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)
O driver é detectado automaticamente pela identidade da conexão, caso não seja informado. Passe um IdentityRuleSet quando a identidade da linha específica da tabela precisar substituir os padrões do esquema.
capture(string $name, array $tables = [], array $filters = []): Snapshot
Capture o estado atual. Passe nomes de tabelas para limitar o escopo. Passe objetos de filtro para excluir tabelas, colunas ou linhas.
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
Capture tabelas físicas usando nomes canônicos de tabela. Use isso quando a mesma tabela lógica tiver nomes físicos diferentes entre ambientes.
$snapshot = $snapshotter->captureAliased('sandbox', [
'wp_onumia_abcd_posts' => 'wp_posts',
'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
O snapshot resultante armazena nomes canônicos de tabela e preserva o esquema físico da tabela, as colunas de identidade configuradas, as chaves de linha e os filtros.
fromData(string $name, array $tableData): Snapshot (estático)
Crie um snapshot a partir de dados brutos, sem conexão com o banco de dados. Útil para testes ou para importar dados de outra fonte.
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 é um objeto de valor somente leitura que representa um estado completo do banco de dados.
$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 contém as linhas, as impressões digitais e o esquema de uma tabela.
$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 são dados 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 as colunas usadas para criar chaves de linha.
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)
Resolve a regra de identidade padrão a partir do esquema: chave primária, primeira chave exclusiva e, em seguida, alternativa baseada no conteúdo.
toArray(): array
Serializa a regra como ['type' => string, 'columns' => list<string>].
fromArray(array $data): IdentityRule (estático)
Hidrata uma regra a partir de dados serializados.
IdentityRuleSet
Merql\Identity\IdentityRuleSet armazena substituições de identidade específicas de cada tabela.
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
Retorna um novo conjunto de regras com uma substituição de tabela adicionada.
conflictsFor(string $table, IdentityRule $rule, array $rows): array
Retorna entradas de IdentityConflict quando várias linhas geram a mesma chave para uma regra.
toArray(): array
Serializa as regras de tabela como um array seguro para JSON.
fromArray(array $data): IdentityRuleSet (estático)
Hidrata um conjunto de regras a partir de dados serializados.
SnapshotStore
Merql\Snapshot\SnapshotStore persiste e carrega snapshots como arquivos JSON.
setDirectory(string $directory): void
Define o diretório de armazenamento. O padrão é .merql/snapshots.
save(Snapshot $snapshot): void
Grava um snapshot no disco.
load(string $name): Snapshot
Carrega um snapshot pelo nome. Lança SnapshotException se não for encontrado.
exists(string $name): bool
Verifica se um arquivo de snapshot existe.
delete(string $name): void
Remove um arquivo de snapshot.
Comparador
Merql\Diff\Differ compara dois snapshots e produz um conjunto de alterações.
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 dois valores convertendo-os em strings. NULL é tratado como um valor distinto: NULL === NULL é verdadeiro, NULL === '' é falso.
Conjunto de alterações
Merql\Diff\Changeset é uma coleção somente leitura de operações no nível da linha.
$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 somente leitura que representam alterações individuais em linhas.
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 uma única alteração de coluna em uma atualização de linha.
$diff->column; // string
$diff->oldValue; // mixed
$diff->newValue; // mixed
ThreeWayMerge
Merql\Merge\ThreeWayMerge é o algoritmo central de mesclagem.
__construct(?CellMergeConfig $cellMergeConfig = null)
Passe um CellMergeConfig para ativar a mesclagem no nível da célula para colunas TEXT e 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 uma mesclagem de três vias. Calcula internamente os conjuntos de alterações (da base para o nosso lado e da base para o lado deles) e os mescla.
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
patch(Snapshot $base, Snapshot $changes): MergeResult
Atalho para mesclagem de duas vias. Equivalente a merge($base, $base, $changes).
schemaMismatches(): array
Retorna SchemaException[] para quaisquer diferenças de esquema detectadas durante a última mesclagem (colunas adicionadas ou removidas entre snapshots).
MergeResult
Merql\Merge\MergeResult é um objeto de valor somente leitura que contém o resultado da mesclagem.
$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 é um modelo de revisão serializável para uma mesclagem.
$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>
Use MergePlanSerializer::toJson() e ::fromJson() para garantir roundtrips estáveis de JSON.
Seleções
ChangeGroupSelection armazena IDs de grupos de alterações preparados. Converta-o em um OperationSelection ao criar resultados de mesclagem selecionados ou planos de reversão.
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 armazena operações inversas e expectativas de divergência para uma aplicação selecionada.
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;
$rollback = (new RollbackPlanBuilder())->build(
'rollback-1',
$plan,
$operationSelection,
$liveBeforeRows,
);
$json = RollbackPlanSerializer::toJson($rollback);
Use RollbackDrift antes de aplicar a reversão e, em seguida, RollbackPlanApplier para executar as operações inversas.
MergeOperation
Merql\Merge\MergeOperation é uma única operação resolvida da mesclagem.
$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'
Conflito
Merql\Merge\Conflict representa um conflito de mesclagem não resolvido.
$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 conflito
| Tipo | Significado |
|---|---|
update_update |
Ambos os lados alteraram a mesma coluna para valores diferentes |
update_delete |
Nosso lado atualizou uma linha que o lado deles excluiu |
delete_update |
Nosso lado excluiu uma linha que o lado deles atualizou |
insert_insert |
Ambos os lados inseriram uma linha com a mesma chave primária |
ConflictResolver
Merql\Merge\ConflictResolver aplica uma política de resolução a todos os conflitos no resultado da mesclagem.
resolve(MergeResult $result, ConflictPolicy $policy): MergeResult (estático)
Retorna um novo MergeResult com os conflitos resolvidos de acordo com a 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 é um enum baseado com três 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 executa a lógica de mesclagem por coluna para uma única linha. Usado internamente por ThreeWayMerge.
merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array (estático)
Retorna ['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 executa um resultado de mesclagem em um banco de dados.
__construct(DatabaseConnection $connection, ?Driver $driver = null)
apply(MergeResult $result, ?Snapshot $base = null): ApplyResult
Executa todas as operações em uma única transação. Reverte a transação em caso de erro e o informa em ApplyResult::errors() em vez de lançar uma exceção. Lança ConflictException se o resultado tiver conflitos não resolvidos.
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());
}
A ordem das chaves estrangeiras é gerenciada automaticamente: as inserções são ordenadas conforme as dependências, e as exclusões, na ordem inversa das dependências.
ApplyResult
Merql\Apply\ApplyResult é um objeto de valor somente leitura retornado pelo aplicador.
$applied->rowsAffected(); // int
$applied->errors(); // list<string>
$applied->hasErrors(); // bool
Após uma aplicação revertida, rowsAffected() ainda contabiliza as instruções executadas antes da falha (#4). Confira hasErrors() antes de confiar na contagem.
DryRun
Merql\Apply\DryRun gera SQL legível por humanos sem executar nada.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (estático)
Retorna uma lista de strings SQL com os valores inseridos diretamente (apenas para exibição).
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 gera instruções SQL parametrizadas. É usado internamente tanto por Applier quanto por DryRun.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (estático)
Retorna 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 estratégias de mesclagem no nível da célula para colunas ou tipos de coluna específicos.
auto(): self (estático)
Fábrica de conveniência que atribui TextCellMerger a colunas TEXT/LONGTEXT e JsonCellMerger a colunas JSON.
use Merql\CellMerge\CellMergeConfig;
$config = CellMergeConfig::auto();
forColumn(string $column, CellMerger $merger): self
Atribui um mesclador a uma coluna específica. Use table.column para nomes qualificados ou apenas column para todas as tabelas.
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
Atribui um mesclador a todas as colunas que correspondem a um padrão de tipo (correspondência de substring sem distinção entre maiúsculas e 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 o mesclador para uma determinada tabela, coluna e tipo. Prioridade: nome de coluna qualificado, nome de coluna não qualificado, padrão de tipo, padrão (opaco).
CellMerger (interface)
Merql\CellMerge\CellMerger é a interface para estratégias de mesclagem no nível da célula.
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;
interface CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
São fornecidas três implementações integradas.
TextCellMerger
Merql\CellMerge\TextCellMerger mescla textos com várias linhas usando uma comparação de três vias, linha por linha (o mesmo algoritmo que git usa para o conteúdo de arquivos). Quando os dois lados editam linhas diferentes, a mesclagem é limpa. Quando os dois lados editam a mesma linha de maneiras diferentes, é informado um conflito.
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 mescla valores JSON comparando as chaves de nível superior de forma independente. Cada chave é mesclada usando a lógica de três vias. Objetos aninhados são comparados como strings JSON opacas (não são mesclados recursivamente).
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 é o padrão. Trata os valores como strings opacas e sempre informa um conflito quando são diferentes.
CellMergeResult
Merql\CellMerge\CellMergeResult é o tipo de retorno de todos os mescladores de células.
$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 é uma representação somente leitura da estrutura da tabela.
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 drivers
merql usa um sistema de drivers plugável para abstrair operações específicas de cada banco de dados. Os drivers são detectados automaticamente pela identidade do driver da conexão.
Driver (interface)
Merql\Driver\Driver define o contrato que todos os drivers de banco de dados implementam.
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;
}
Implementações integradas: MysqlDriver e SqliteDriver.
DriverFactory
Merql\Driver\DriverFactory detecta automaticamente o driver a partir de uma conexão.
use Merql\Driver\DriverFactory;
$driver = DriverFactory::create($connection);
Registre um driver personalizado para outros bancos de dados:
DriverFactory::register('pgsql', MyPostgresDriver::class);
Filtros
TableFilter
Merql\Filter\TableFilter inclui ou exclui tabelas usando padrões glob.
use Merql\Filter\TableFilter;
$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
ColumnFilter
Merql\Filter\ColumnFilter remove colunas específicas dos snapshots.
use Merql\Filter\ColumnFilter;
$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
RowFilter
Merql\Filter\RowFilter filtra linhas usando uma função 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;
});
Exceções
SnapshotException
Merql\Exceptions\SnapshotException é lançado quando um snapshot não pode ser encontrado ou tem um nome inválido.
SnapshotException::notFound('baseline', '/path/to/baseline.json');
MergeException
Merql\Exceptions\MergeException é lançado quando falta um snapshot necessário para uma mesclagem.
MergeException::snapshotRequired('base');
ConflictException
Merql\Exceptions\ConflictException é lançado ao tentar aplicar um resultado de mesclagem com conflitos não resolvidos.
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
SchemaException
Merql\Exceptions\SchemaException é lançado (ou coletado) quando os esquemas diferem entre snapshots.
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
Incompatibilidades de esquema não interrompem a mesclagem. Elas são coletadas e ficam disponíveis em $result->schemaMismatches() para inspeção.