Справочник 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(); вызовите ещё раз, чтобы переключить базу данных, например, для создания снимков нашей и другой сторон из двух разных копий. Если не передан $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
Двустороннее слияние: применяет изменения к исходной версии. Эквивалентно merge($base, $base, $changes). Всегда даёт результат без конфликтов, поскольку одновременных правок нет. Только на уровне столбцов, как и merge().
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
Выполняет результат слияния без конфликтов для базы данных, переданной в init(). Требуется init(). Выдаёт исключение ConflictException, если остались неразрешённые конфликты.
Выполняются все операции результата, поэтому полный результат merge() корректно применяется только к базе данных, в которой всё ещё хранится исходное состояние. Если база данных уже содержит одну из сторон, примените только отсутствующие в ней операции; см. Применение с проверками.
$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 — это снимок целевой базы данных (другой стороны, если наша версия переносится в базу, из которой был создан снимок другой стороны). Если какой-либо запрос затрагивает ноль строк, транзакция откатывается.
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
Подключается по любой строке DSN для PDO. В merql есть драйверы только для mysql и sqlite; для других DSN подключение устанавливается, но Merql::init(), Snapshotter и средства применения выдают Unsupported database driver, если драйвер не зарегистрирован через DriverFactory::register().
$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 — истина, NULL === '' — ложь.
Набор изменений
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
Выполняет трёхстороннее слияние. Внутри вычисляет наборы изменений (от исходной версии к нашей и от исходной версии к другой) и объединяет их.
$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 |
Наша сторона обновила строку, удалённую другой стороной |
delete_update |
Наша сторона удалила строку, обновлённую другой стороной |
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 (статический метод)
Фабричный метод для удобства: назначает TextCellMerger столбцам TEXT/LONGTEXT и JsonCellMerger столбцам JSON.
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 включает таблицы или исключает их по глоб-шаблонам.
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().