PHP API 参考
merql 所有公共类和方法的完整参考文档。
Merql(门面)
Merql\Merql 是常见操作的静态入口。它封装了底层类,并自动管理快照持久化。
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
使用数据库连接初始化门面。必须在调用 snapshot()、apply() 或 applyGuarded() 之前调用;如需切换数据库,请再次调用,例如从两个不同副本创建 ours 和 theirs 的快照。如果没有提供 $driver,它会调用 DriverFactory::create();对于 MySQL 和 SQLite 以外的引擎,如果没有注册驱动程序,该方法会抛出异常。传入驱动程序可覆盖自动检测;传入标识规则集可覆盖快照捕获时各表的行标识规则。
snapshot(string $name, array $tables = []): Snapshot
捕获当前数据库状态并将其持久化到磁盘。传入表名数组可对指定表创建快照;省略则对所有表创建快照。
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
diff(string $base, string $current): Changeset
加载两个命名快照并计算它们之间的变更集。由于该方法基于持久化快照工作,因此不需要 init()。
$changeset = Merql::diff('before', 'after');
merge(string $base, string $ours, string $theirs): MergeResult
对三个命名快照执行三方合并。返回包含无冲突操作和所有冲突的结果。按列合并:门面会构建 ThreeWayMerge,但不包含 CellMergeConfig,因此不会应用单元格级合并。如需此功能,请直接使用 ThreeWayMerge。
$result = Merql::merge('base', 'ours', 'theirs');
patch(string $base, string $changes): MergeResult
双向合并:将更改应用到 base。等同于 merge($base, $base, $changes)。由于没有并发编辑,因此始终会生成无冲突结果。仅按列合并,与 merge() 一样。
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
对传入 init() 的数据库执行无冲突合并结果。需要 init()。如果仍有未解决的冲突,则抛出 ConflictException。
结果中的每项操作都会执行,因此 merge() 的完整结果只有在数据库仍处于 base 状态时才能干净地应用。对于已经包含一侧更改的数据库,请选择应用其缺少的操作;请参阅 受保护的应用。
$applied = Merql::apply($result);
echo $applied->rowsAffected();
plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan
根据三个命名快照构建可供 UI 使用的合并计划。该计划包含稳定的操作 ID、更改组 ID、操作上下文、冲突、架构不匹配信息、摘要、元数据和计划哈希。与 merge() 一样,此合并按列进行;如需按单元格合并,请传入经过单元格合并的结果给 MergePlanBuilder::build()。
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
'source' => 'review-ui',
]);
applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult
使用乐观实时行前置条件应用无冲突合并结果。更新和删除要求当前实时行与 $expectedLive 匹配;插入要求目标标识尚不存在。$expectedLive 是目标数据库的快照(将 ours 提升到创建 theirs 快照的数据库时,即为 theirs)。任何影响零行的语句都会回滚事务。
use Merql\Snapshot\SnapshotStore;
$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
reset(): void
清除内部连接和 Snapshotter 实例。主要用于在测试运行之间重置状态。
连接
Merql\Connection 会使用合理的默认设置构建连接适配器。PDO 连接在发生错误时会抛出异常,使用关联数组提取模式、不模拟预处理,并将提取值转换为字符串。mysqli 适配器会将提取值规范化为相同的字符串或 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
创建一个 SQLite 连接。通过 PRAGMA foreign_keys = ON 自动启用外键。
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection
使用任意 PDO DSN 字符串进行连接。merql 仅附带 mysql 和 sqlite 的驱动程序;其他 DSN 可以建立连接,但如果未通过 DriverFactory::register() 注册驱动程序,Merql::init()、Snapshotter 和 applier 都会抛出 Unsupported database driver。
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
fromPdo(PDO $pdo): PdoDatabaseConnection
包装现有的 PDO 实例。
fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection
包装现有的 mysqli 实例。
适配器契约
所有适配器都实现 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() 和 scalar() 接受位置参数 list<scalar|null>,
用于 ? 占位符。query() 返回
list<array<string, scalar|null>>;PDO 和 mysqli 适配器会将值规范化为相同的
字符串或 null 格式。
快照工具
Merql\Snapshot\Snapshotter 用于捕获数据库状态。与门面不同,它让你可以完全控制筛选条件,并且不会自动持久化。
__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)
如果未指定,驱动程序会根据连接标识自动检测。如果需要覆盖架构
默认设置中的表级行标识,请传入 IdentityRuleSet。
capture(string $name, array $tables = [], array $filters = []): Snapshot
捕获当前状态。传入表名可限制范围。传入筛选器对象可排除表、列或行。
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
在规范表名下捕获物理表。当同一逻辑表在不同环境中具有不同的物理名称时,请使用此功能。
$snapshot = $snapshotter->captureAliased('sandbox', [
'wp_onumia_abcd_posts' => 'wp_posts',
'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
生成的快照会存储规范表名,同时保留物理表架构、配置的标识列、行键和筛选条件。
fromData(string $name, array $tableData): Snapshot(静态)
从原始数据构建快照,无需数据库连接。适用于测试或从其他来源导入数据。
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']),
]);
快照
Merql\Snapshot\Snapshot 是一个只读值对象,表示完整的数据库状态。
$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 保存一个表的行、指纹和架构。
$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 是 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 定义用于构建行键的列。
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(静态)
根据架构解析默认标识规则:主键、首个唯一键,然后回退到内容。
toArray(): array
将规则序列化为 ['type' => string, 'columns' => list<string>]。
fromArray(array $data): IdentityRule(静态)
从序列化数据还原规则。
IdentityRuleSet
Merql\Identity\IdentityRuleSet 存储表级标识覆盖规则。
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
返回一个新增了单表覆盖规则的新规则集。
conflictsFor(string $table, IdentityRule $rule, array $rows): array
当多行根据某条规则生成相同键时,返回 IdentityConflict 条目。
toArray(): array
将表规则序列化为兼容 JSON 的数组。
fromArray(array $data): IdentityRuleSet(静态)
从序列化数据还原规则集。
SnapshotStore
Merql\Snapshot\SnapshotStore 以 JSON 文件的形式持久化和加载快照。
setDirectory(string $directory): void
设置存储目录。默认值为 .merql/snapshots。
save(Snapshot $snapshot): void
将快照写入磁盘。
load(string $name): Snapshot
按名称加载快照。如果未找到,则抛出 SnapshotException。
exists(string $name): bool
检查快照文件是否存在。
delete(string $name): void
删除快照文件。
差异比较
Merql\Diff\Differ 比较两个快照并生成变更集。
diff(Snapshot $base, Snapshot $current): Changeset
use Merql\Diff\Differ;
$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
valuesEqual(mixed $a, mixed $b): bool(静态)
使用字符串强制转换比较两个值。NULL 被视为独立值:NULL === NULL 为 true,NULL === '' 为 false。
变更集
Merql\Diff\Changeset 是一个只读集合,包含行级操作。
$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
表示单行变更的只读值对象。
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 表示行更新中的单列变更。
$diff->column; // string
$diff->oldValue; // mixed
$diff->newValue; // mixed
ThreeWayMerge
Merql\Merge\ThreeWayMerge 是核心合并算法。
__construct(?CellMergeConfig $cellMergeConfig = null)
传入 CellMergeConfig,即可为 TEXT 和 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
执行三方合并。在内部计算变更集(base 到 ours、base 到 theirs)并将其合并。
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
patch(Snapshot $base, Snapshot $changes): MergeResult
双向合并快捷方式。等同于 merge($base, $base, $changes)。
schemaMismatches(): array
返回上次合并期间检测到的任何架构差异对应的 SchemaException[](快照之间新增或移除了列)。
MergeResult
Merql\Merge\MergeResult 是包含合并结果的只读值对象。
$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 是用于合并的可序列化审查模型。
$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>
使用 MergePlanSerializer::toJson() 和 ::fromJson(),实现稳定的 JSON
往返转换。
选择
ChangeGroupSelection 存储已暂存变更组的 ID。构建所选合并结果或回滚计划时,将其转换为
OperationSelection。
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 存储逆向操作和所选应用的漂移预期。
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;
$rollback = (new RollbackPlanBuilder())->build(
'rollback-1',
$plan,
$operationSelection,
$liveBeforeRows,
);
$json = RollbackPlanSerializer::toJson($rollback);
应用回滚之前使用 RollbackDrift,然后使用 RollbackPlanApplier 执行
逆向操作。
MergeOperation
Merql\Merge\MergeOperation 是合并中一个已解决的操作。
$op->type; // string: 'insert' | 'update' | 'delete'
$op->table; // string
$op->rowKey; // string
$op->values; // array<string, mixed>
$op->source; // string: 'ours' | 'theirs' | 'merged'
类型常量:
MergeOperation::TYPE_INSERT; // 'insert'
MergeOperation::TYPE_UPDATE; // 'update'
MergeOperation::TYPE_DELETE; // 'delete'
冲突
Merql\Merge\Conflict 表示尚未解决的合并冲突。
$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
冲突类型
| 类型 | 含义 |
|---|---|
update_update |
两侧将同一列更改为不同的值 |
update_delete |
ours 更新了 theirs 已删除的行 |
delete_update |
ours 删除了 theirs 已更新的行 |
insert_insert |
两侧插入了主键相同的行 |
ConflictResolver
Merql\Merge\ConflictResolver 将解决策略应用于合并结果中的所有冲突。
resolve(MergeResult $result, ConflictPolicy $policy): MergeResult(静态)
返回新的 MergeResult,并根据策略解决其中的冲突。
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 是一个有三个分支的后备枚举。
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 执行单行的逐列合并逻辑。由 ThreeWayMerge 在内部使用。
merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array(静态)
返回 ['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)
应用器
Merql\Apply\Applier 将合并结果应用到数据库。
__construct(DatabaseConnection $connection, ?Driver $driver = null)
apply(MergeResult $result, ?Snapshot $base = null): ApplyResult
在单个事务中执行所有操作。发生任何错误时回滚,并将错误报告在 ApplyResult::errors() 中,而不是抛出错误。如果结果中仍有未解决的冲突,则抛出 ConflictException。
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());
}
外键顺序会自动处理:插入操作按依赖顺序排序,删除操作按依赖顺序反向排序。
ApplyResult
Merql\Apply\ApplyResult 是应用器返回的只读值对象。
$applied->rowsAffected(); // int
$applied->errors(); // list<string>
$applied->hasErrors(); // bool
回滚应用后,rowsAffected() 仍会统计失败前已运行的语句(#4)。在信任此计数前,请检查 hasErrors()。
DryRun
Merql\Apply\DryRun 会生成易读的 SQL,但不会执行任何操作。
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array(静态)
返回 SQL 字符串列表,其中的值已内联(仅用于显示)。
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 会生成带参数的 SQL 语句。由 Applier 和 DryRun 在内部使用。
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array(静态)
返回 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 用于配置特定列或列类型的单元格级合并策略。
auto(): self(静态)
便捷工厂方法:为 TEXT/LONGTEXT 列指定 TextCellMerger,为 JSON 列指定 JsonCellMerger。
use Merql\CellMerge\CellMergeConfig;
$config = CellMergeConfig::auto();
forColumn(string $column, CellMerger $merger): self
为特定列指定合并器。限定名称请使用 table.column,要匹配所有表则仅使用 column。
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
为所有匹配类型模式的列指定合并器(不区分大小写的子串匹配)。
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
$config = (new CellMergeConfig())
->forType('json', new JsonCellMerger());
getMerger(string $table, string $column, string $columnType = ''): CellMerger
查找给定表、列和类型对应的合并器。优先级:限定列名、非限定列名、类型模式、默认值(不透明)。
CellMerger(接口)
Merql\CellMerge\CellMerger 是单元格级合并策略的接口。
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;
interface CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
提供三种内置实现。
TextCellMerger
Merql\CellMerge\TextCellMerger 使用三方逐行差异比较来合并多行文本(与 git 用于文件内容的算法相同)。两侧编辑不同的行时,合并不会产生冲突。两侧以不同方式编辑同一行时,则会报告冲突。
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 通过分别比较顶层键来合并 JSON 值。每个键都使用三方逻辑合并。嵌套对象会被视为不透明的 JSON 字符串进行比较(不会递归合并)。
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 是默认实现。它将值视为不透明字符串,并且只要值不同就始终报告冲突。
CellMergeResult
Merql\CellMerge\CellMergeResult 是所有单元格合并器的返回类型。
$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 是表结构的只读表示。
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>
驱动系统
merql 使用可插拔的驱动系统来抽象数据库特定操作。系统会根据连接的驱动标识自动检测驱动。
Driver(接口)
Merql\Driver\Driver 定义了所有数据库驱动必须实现的契约。
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;
}
内置实现:MysqlDriver 和 SqliteDriver。
DriverFactory
Merql\Driver\DriverFactory 会根据连接自动检测驱动。
use Merql\Driver\DriverFactory;
$driver = DriverFactory::create($connection);
为其他数据库注册自定义驱动:
DriverFactory::register('pgsql', MyPostgresDriver::class);
过滤器
TableFilter
Merql\Filter\TableFilter 使用 glob 模式包含或排除表。
use Merql\Filter\TableFilter;
$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
ColumnFilter
Merql\Filter\ColumnFilter 从快照中移除特定列。
use Merql\Filter\ColumnFilter;
$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
RowFilter
Merql\Filter\RowFilter 使用谓词函数筛选行。
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;
});
异常
SnapshotException
找不到快照或快照名称无效时,会抛出 Merql\Exceptions\SnapshotException。
SnapshotException::notFound('baseline', '/path/to/baseline.json');
MergeException
合并缺少必需的快照时,会抛出 Merql\Exceptions\MergeException。
MergeException::snapshotRequired('base');
ConflictException
尝试应用仍有未解决冲突的合并结果时,会抛出 Merql\Exceptions\ConflictException。
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
SchemaException
快照之间的架构不同时,会抛出(或收集)Merql\Exceptions\SchemaException。
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
架构不匹配不会中止合并。系统会收集这些问题,可通过 $result->schemaMismatches() 查看。