コンテンツへスキップ

PHP APIリファレンス

merqlのすべての公開クラスとメソッドの完全なリファレンスです。

Merql(ファサード)

Merql\Merqlは、一般的な操作のための静的エントリーポイントです。低レベルのクラスをラップし、スナップショットの永続化を自動で管理します。

PHP
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

現在のデータベース状態をキャプチャし、ディスクに保存します。特定のテーブルをスナップショットするにはテーブル名の配列を渡し、すべてのテーブルを対象にするには省略します。

PHP
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);

diff(string $base, string $current): Changeset

名前を指定した2つのスナップショットを読み込み、その間の変更セットを計算します。保存済みスナップショットを使うため、init()は必要ありません。

PHP
$changeset = Merql::diff('before', 'after');

merge(string $base, string $ours, string $theirs): MergeResult

名前を指定した3つのスナップショットを3方向にマージします。競合のない操作と競合を含む結果を返します。カラムレベルでマージします。ファサードはCellMergeConfigを指定せずにThreeWayMergeを構築するため、セルレベルのマージは適用されません。セルレベルのマージには、ThreeWayMergeを直接使用してください。

PHP
$result = Merql::merge('base', 'ours', 'theirs');

patch(string $base, string $changes): MergeResult

2方向マージ:変更をbaseに適用します。merge($base, $base, $changes)と同等です。並行編集がないため、常に競合のない結果になります。merge()と同様にカラムレベルです。

PHP
$result = Merql::patch('base', 'changes');

apply(MergeResult $result): ApplyResult

init()に渡されたデータベースに、競合のないマージ結果を適用します。init()が必要です。未解決の競合が残っている場合はConflictExceptionをスローします。

結果内のすべての操作が実行されるため、merge()の結果全体を正常に適用できるのは、base状態を保持しているデータベースだけです。すでに片側の状態を保持しているデータベースには、不足している操作を選択して適用してください。条件付き適用を参照してください。

PHP
$applied = Merql::apply($result);
echo $applied->rowsAffected();

plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan

名前を指定した3つのスナップショットから、UI対応のマージプランを作成します。プランには、安定した操作ID、変更グループID、操作コンテキスト、競合、スキーマの不一致、概要、メタデータ、プランハッシュが含まれます。merge()と同様にカラムレベルでマージします。セルレベルのマージには、セルマージ済みの結果をMergePlanBuilder::build()に渡してください。

PHP
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
    'source' => 'review-ui',
]);

applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult

ライブ行に対する楽観的な事前条件を使って、競合のないマージ結果を適用します。更新と削除では、現在のライブ行が$expectedLiveと一致する必要があります。挿入では、対象の識別情報がまだ存在しないことが必要です。$expectedLiveは対象データベース(oursを、theirsのスナップショット元のデータベースに反映する場合はtheirs)のスナップショットです。影響を受けた行が0件のSQL文があると、トランザクション全体がロールバックされます。

PHP
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

PHP
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

PHP
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');

sqlite(string $path = ':memory:'): PdoDatabaseConnection

SQLite接続を作成します。PRAGMA foreign_keys = ONによって外部キーを自動的に有効にします。

PHP
$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、適用処理はUnsupported database driverをスローします。

PHP
$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を実装します。

PHP
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

現在の状態をキャプチャします。対象を絞るにはテーブル名を渡します。テーブル、カラム、行を除外するにはフィルターオブジェクトを渡します。

PHP
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

物理テーブルを正規のテーブル名でキャプチャします。同じ論理テーブルの物理名が環境ごとに異なる場合に使用します。

PHP
$snapshot = $snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
    'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);

生成されたスナップショットは正規のテーブル名を保存しつつ、物理テーブルのスキーマ、設定された識別カラム、行キー、フィルターも保持します。

fromData(string $name, array $tableData): Snapshot(静的)

データベース接続なしで、未加工データからスナップショットを作成します。テストや別のソースからデータをインポートする場合に便利です。

PHP
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は、データベースの完全な状態を表す読み取り専用の値オブジェクトです。

PHP
$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 は1つのテーブルの行、フィンガープリント、スキーマを保持します。

PHP
$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() の入力データです。

PHP
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 は行キーの構築に使用する列を定義します。

PHP
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 はテーブル固有の識別ルールの上書きを保存します。

PHP
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

テーブルの上書きを1つ追加した新しいルールセットを返します。

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 は2つのスナップショットを比較し、変更セットを生成します。

diff(Snapshot $base, Snapshot $current): Changeset

PHP
use Merql\Diff\Differ;

$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);

valuesEqual(mixed $a, mixed $b): bool(静的)

文字列に変換して2つの値を比較します。NULLは別個の値として扱われます。NULL === NULL はtrue、NULL === '' はfalseです。


変更セット

Merql\Diff\Changeset は行単位の操作をまとめた読み取り専用コレクションです。

PHP
$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

PHP
$insert->table;                    // string
$insert->rowKey;                   // string
$insert->values;                   // array<string, mixed>

RowUpdate

PHP
$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

PHP
$delete->table;                    // string
$delete->rowKey;                   // string
$delete->oldValues;                // array<string, mixed>: values before deletion

ColumnDiff

Merql\Diff\ColumnDiff は行の更新における単一の列変更を表します。

PHP
$diff->column;                     // string
$diff->oldValue;                   // mixed
$diff->newValue;                   // mixed

ThreeWayMerge

Merql\Merge\ThreeWayMerge は中核となるマージアルゴリズムです。

__construct(?CellMergeConfig $cellMergeConfig = null)

CellMergeConfig を渡すと、TEXT列と JSON 列でセル単位のマージが有効になります。

PHP
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

3方向マージを実行します。変更セット(ベースから自分側、ベースから相手側)を内部で計算し、マージします。

PHP
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);

patch(Snapshot $base, Snapshot $changes): MergeResult

2方向マージの簡易版です。merge($base, $base, $changes) と同等です。

schemaMismatches(): array

直近のマージで検出されたスキーマの差異(スナップショット間で追加または削除された列)について、SchemaException[] を返します。


MergeResult

Merql\Merge\MergeResult はマージ結果を含む読み取り専用の値オブジェクトです。

PHP
$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 はマージを確認するためのシリアライズ可能なモデルです。

PHP
$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>

安定した JSON のラウンドトリップには、MergePlanSerializer::toJson() と ::fromJson() を使用してください。

選択項目

ChangeGroupSelection はステージ済みの変更グループIDを保存します。選択したマージ結果やロールバック計画を作成するときに、OperationSelection に変換します。

PHP
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 は、選択された適用に対する逆操作とドリフトの予測値を保存します。

PHP
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 はマージで解決された単一の操作です。

PHP
$op->type;                         // string: 'insert' | 'update' | 'delete'
$op->table;                        // string
$op->rowKey;                       // string
$op->values;                       // array<string, mixed>
$op->source;                       // string: 'ours' | 'theirs' | 'merged'

型定数:

PHP
MergeOperation::TYPE_INSERT;       // 'insert'
MergeOperation::TYPE_UPDATE;       // 'update'
MergeOperation::TYPE_DELETE;       // 'delete'

競合

Merql\Merge\Conflict は未解決のマージ競合を表します。

PHP
$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 を返します。

PHP
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 は3つのケースを持つバックドenumです。

PHP
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 は1行に対して列ごとのマージ処理を行います。内部で ThreeWayMerge に使用されます。

merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array(静的)

['values' => array, 'conflicts' => list<Conflict>] を返します。

PHP
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

すべての操作を1つのトランザクション内で実行します。エラーが発生するとロールバックし、スローせずに ApplyResult::errors() でエラーを報告します。結果に未解決の競合がある場合は ConflictException をスローします。

PHP
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 はapplierが返す読み取り専用の値オブジェクトです。

PHP
$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 文字列のリストを返します(表示専用)。

PHP
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>}> を返します。

PHP
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 を割り当てる便利なファクトリーです。

PHP
use Merql\CellMerge\CellMergeConfig;

$config = CellMergeConfig::auto();

forColumn(string $column, CellMerger $merger): self

特定の列にマージャーを割り当てます。修飾名には table.column、すべてのテーブルには column を使用してください。

PHP
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

型パターンに一致するすべての列にマージャーを割り当てます(大文字と小文字を区別しない部分文字列一致)。

PHP
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 はセル単位のマージ戦略のインターフェースです。

PHP
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

interface CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}

組み込みの実装が3つ用意されています。

TextCellMerger

Merql\CellMerge\TextCellMerger は3方向の行単位diff(gitがファイル内容に使うものと同じアルゴリズム)を使用して、複数行のテキストをマージします。両側が異なる行を編集した場合、競合なくマージされます。同じ行を異なる内容に編集した場合は、競合として報告されます。

PHP
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 の値をトップレベルのキーごとに比較してマージします。各キーは3方向のロジックでマージされます。ネストされたオブジェクトは不透明な JSON 文字列として比較され、再帰的にはマージされません。

PHP
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 はすべてのセルマージャーの戻り値の型です。

PHP
$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 はテーブル構造を読み取り専用で表現したものです。

PHP
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 はすべてのデータベースドライバーが実装する契約を定義します。

PHP
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 は接続からドライバーを自動検出します。

PHP
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);

その他のデータベース用にカスタムドライバーを登録します:

PHP
DriverFactory::register('pgsql', MyPostgresDriver::class);

フィルター

TableFilter

Merql\Filter\TableFilter はglobパターンを使ってテーブルを含めるか除外します。

PHP
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);

ColumnFilter

Merql\Filter\ColumnFilter はスナップショットから特定の列を除外します。

PHP
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);

RowFilter

Merql\Filter\RowFilter は述語関数で行をフィルタリングします。

PHP
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 はスナップショットが見つからない場合、または名前が無効な場合にスローされます。

PHP
SnapshotException::notFound('baseline', '/path/to/baseline.json');

MergeException

Merql\Exceptions\MergeException はマージに必要なスナップショットがない場合にスローされます。

PHP
MergeException::snapshotRequired('base');

ConflictException

Merql\Exceptions\ConflictException は未解決の競合があるマージ結果を適用しようとした場合にスローされます。

PHP
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"

SchemaException

Merql\Exceptions\SchemaException はスナップショット間でスキーマが異なる場合にスローされるか、収集されます。

PHP
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');

スキーマの不一致があってもマージは停止しません。不一致は収集され、$result->schemaMismatches() から確認できます。