PHP API-Referenz
Vollständige Referenz aller öffentlichen Klassen und Methoden in merql.
Merql (Fassade)
Merql\Merql ist der statische Einstiegspunkt für gängige Vorgänge. Die Klasse kapselt die nachgeordneten Klassen und verwaltet die dauerhafte Speicherung von Snapshots automatisch.
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
Initialisiere die Fassade mit einer Datenbankverbindung. Dies muss vor snapshot(), apply() oder applyGuarded() erfolgen. Rufe die Methode erneut auf, um die Datenbank zu wechseln, zum Beispiel, um unsere und ihre Snapshots aus zwei verschiedenen Kopien zu erstellen. Ohne $driver ruft die Methode DriverFactory::create() auf. Für andere Engines als MySQL und SQLite wird eine Ausnahme ausgelöst, sofern kein Treiber registriert ist. Übergib einen Treiber, um die automatische Erkennung zu überschreiben, oder einen Identitätsregelsatz, um die Zeilenidentität pro Tabelle während der Snapshot-Erfassung festzulegen.
snapshot(string $name, array $tables = []): Snapshot
Erfasse den aktuellen Datenbankzustand und speichere ihn dauerhaft auf dem Datenträger. Übergib ein Array mit Tabellennamen, um bestimmte Tabellen als Snapshot zu sichern, oder lass es weg, um alle Tabellen einzubeziehen.
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
diff(string $base, string $current): Changeset
Lade zwei benannte Snapshots und berechne die Änderungen zwischen ihnen. init() ist nicht erforderlich, da mit dauerhaft gespeicherten Snapshots gearbeitet wird.
$changeset = Merql::diff('before', 'after');
merge(string $base, string $ours, string $theirs): MergeResult
Dreiwege-Merge von drei benannten Snapshots. Gibt ein Ergebnis mit konfliktfreien Vorgängen und etwaigen Konflikten zurück. Der Merge erfolgt auf Spaltenebene: Die Fassade erstellt ThreeWayMerge ohne CellMergeConfig, daher wird kein Merge auf Zellenebene angewendet. Verwende dafür direkt ThreeWayMerge.
$result = Merql::merge('base', 'ours', 'theirs');
patch(string $base, string $changes): MergeResult
Zweiwege-Merge: Änderungen auf die Basis anwenden. Entspricht merge($base, $base, $changes). Liefert immer ein konfliktfreies Ergebnis, da es keine gleichzeitigen Bearbeitungen gibt. Wie bei merge() nur auf Spaltenebene.
$result = Merql::patch('base', 'changes');
apply(MergeResult $result): ApplyResult
Wende ein konfliktfreies Merge-Ergebnis auf die Datenbank an, die an init() übergeben wurde. init() ist erforderlich. Löst ConflictException aus, wenn ungelöste Konflikte bestehen bleiben.
Jeder Vorgang im Ergebnis wird ausgeführt. Daher lässt sich das vollständige Ergebnis von merge() nur dann problemlos anwenden, wenn die Datenbank noch den Basiszustand enthält. Wenn die Datenbank bereits einen der beiden Zweige enthält, wende nur die Vorgänge an, die dort fehlen; siehe Abgesicherte Anwendung.
$applied = Merql::apply($result);
echo $applied->rowsAffected();
plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan
Erstelle aus drei benannten Snapshots einen für UI geeigneten Merge-Plan. Der Plan enthält stabile Vorgangs-IDs, Änderungsgruppen-IDs, Vorgangskontext, Konflikte, Schemaabweichungen, eine Zusammenfassung, Metadaten und einen Plan-Hash. Wie bei merge() erfolgt der Merge auf Spaltenebene. Übergib für einen Merge auf Zellenebene ein zellweise zusammengeführtes Ergebnis an MergePlanBuilder::build().
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
'source' => 'review-ui',
]);
applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult
Wende ein konfliktfreies Merge-Ergebnis mit optimistischen Vorbedingungen für Live-Zeilen an. Vor Aktualisierungen und Löschungen müssen die aktuellen Live-Zeilen mit $expectedLive übereinstimmen. Vor dem Einfügen darf die Zielidentität noch nicht existieren. $expectedLive ist ein Snapshot der Zieldatenbank (sie, wenn unsere Änderungen in die Datenbank übernommen werden, aus der ihr Snapshot stammt). Wenn eine Anweisung keine Zeilen betrifft, wird die Transaktion zurückgerollt.
use Merql\Snapshot\SnapshotStore;
$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
reset(): void
Setze die internen Verbindungs- und Snapshotter-Instanzen zurück. Wird hauptsächlich in Tests verwendet, um den Zustand zwischen Durchläufen zurückzusetzen.
Verbindung
Merql\Connection erstellt Verbindungsadapter mit sinnvollen Standardeinstellungen. Bei PDO-Verbindungen werden Ausnahmen bei Fehlern, assoziativer Abrufmodus, keine emulierten vorbereiteten Anweisungen und Stringkonvertierung beim Abruf verwendet. Der mysqli-Adapter normalisiert abgerufene Werte auf dieselbe Zeichenfolge-oder-null-Form.
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
Erstellt eine SQLite-Verbindung. Aktiviert Fremdschlüssel automatisch über PRAGMA foreign_keys = ON.
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection
Stelle eine Verbindung mit einer beliebigen PDO-DSN-Zeichenfolge her. merql enthält nur Treiber für mysql und sqlite. Andere DSNs stellen zwar eine Verbindung her, aber Merql::init(), Snapshotter und die Applier lösen Unsupported database driver aus, sofern kein Treiber mit DriverFactory::register() registriert ist.
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
fromPdo(PDO $pdo): PdoDatabaseConnection
Umschließt eine vorhandene PDO-Instanz.
fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection
Umschließt eine vorhandene mysqli-Instanz.
Adaptervertrag
Alle Adapter implementieren 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() und scalar() akzeptieren positionale list<scalar|null>-Parameter für Platzhalter ?. query() gibt list<array<string, scalar|null>> zurück. Werte werden bei PDO- und mysqli-Adaptern auf dieselbe Zeichenfolge-oder-null-Form normalisiert.
Snapshotter
Merql\Snapshot\Snapshotter erfasst den Datenbankzustand. Anders als die Fassade bietet es dir die volle Kontrolle über Filter und speichert Snapshots nicht automatisch dauerhaft.
__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)
Wenn kein Treiber angegeben ist, wird er anhand der Verbindungsidentität automatisch erkannt. Übergib einen IdentityRuleSet, wenn eine tabellenspezifische Zeilenidentität die Schema-Standardeinstellungen überschreiben soll.
capture(string $name, array $tables = [], array $filters = []): Snapshot
Erfasse den aktuellen Zustand. Übergib Tabellennamen, um den Umfang einzugrenzen. Übergib Filterobjekte, um Tabellen, Spalten oder Zeilen auszuschließen.
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
Erfasse physische Tabellen unter kanonischen Tabellennamen. Verwende dies, wenn dieselbe logische Tabelle in verschiedenen Umgebungen unterschiedliche physische Namen hat.
$snapshot = $snapshotter->captureAliased('sandbox', [
'wp_onumia_abcd_posts' => 'wp_posts',
'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
Der resultierende Snapshot speichert kanonische Tabellennamen und bewahrt zugleich das physische Tabellenschema, konfigurierte Identitätsspalten, Zeilenschlüssel und Filter.
fromData(string $name, array $tableData): Snapshot (statisch)
Erstellt ohne Datenbankverbindung einen Snapshot aus Rohdaten. Nützlich für Tests oder beim Importieren von Daten aus einer anderen Quelle.
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 ist ein schreibgeschütztes Wertobjekt, das einen vollständigen Datenbankzustand darstellt.
$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 enthält die Zeilen, Fingerprints und das Schema einer Tabelle.
$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 sind Eingabedaten für 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 definiert die Spalten, aus denen Zeilenschlüssel erstellt werden.
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 (statisch)
Löst die standardmäßige Identitätsregel anhand des Schemas auf: Primärschlüssel, erster eindeutiger Schlüssel, dann Rückgriff auf den Inhalt.
toArray(): array
Serialisiert die Regel als ['type' => string, 'columns' => list<string>].
fromArray(array $data): IdentityRule (statisch)
Stellt eine Regel aus serialisierten Daten wieder her.
IdentityRuleSet
Merql\Identity\IdentityRuleSet speichert tabellenspezifische Identitätsüberschreibungen.
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
Gibt einen neuen Regelsatz mit einer hinzugefügten Tabellenüberschreibung zurück.
conflictsFor(string $table, IdentityRule $rule, array $rows): array
Gibt IdentityConflict-Einträge zurück, wenn mehrere Zeilen für eine Regel denselben Schlüssel ergeben.
toArray(): array
Serialisiert Tabellenregeln als JSON-sicheres Array.
fromArray(array $data): IdentityRuleSet (statisch)
Stellt einen Regelsatz aus serialisierten Daten wieder her.
SnapshotStore
Merql\Snapshot\SnapshotStore speichert und lädt Snapshots als JSON-Dateien.
setDirectory(string $directory): void
Legt das Speicherverzeichnis fest. Standard ist .merql/snapshots.
save(Snapshot $snapshot): void
Schreibt einen Snapshot auf die Festplatte.
load(string $name): Snapshot
Lädt einen Snapshot anhand seines Namens. Wirft SnapshotException, wenn er nicht gefunden wird.
exists(string $name): bool
Prüft, ob eine Snapshot-Datei existiert.
delete(string $name): void
Entfernt eine Snapshot-Datei.
Differ
Merql\Diff\Differ vergleicht zwei Snapshots und erstellt einen Änderungssatz.
diff(Snapshot $base, Snapshot $current): Changeset
use Merql\Diff\Differ;
$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
valuesEqual(mixed $a, mixed $b): bool (statisch)
Vergleicht zwei Werte nach Umwandlung in Zeichenfolgen. NULL gilt als eigenständiger Wert: NULL === NULL ist true, NULL === '' ist false.
Changeset
Merql\Diff\Changeset ist eine schreibgeschützte Sammlung von Operationen auf Zeilenebene.
$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
Schreibgeschützte Wertobjekte, die einzelne Zeilenänderungen darstellen.
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 stellt eine einzelne Spaltenänderung innerhalb einer Zeilenaktualisierung dar.
$diff->column; // string
$diff->oldValue; // mixed
$diff->newValue; // mixed
ThreeWayMerge
Merql\Merge\ThreeWayMerge ist der zentrale Merge-Algorithmus.
__construct(?CellMergeConfig $cellMergeConfig = null)
Übergib eine CellMergeConfig, um das zellenweise Zusammenführen für TEXT- und JSON-Spalten zu aktivieren.
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
Führt drei Wege zusammen. Berechnet intern Änderungssätze (Basis zu unseren Änderungen, Basis zu deren Änderungen) und führt sie zusammen.
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
patch(Snapshot $base, Snapshot $changes): MergeResult
Kurzform für einen Zwei-Wege-Merge. Entspricht merge($base, $base, $changes).
schemaMismatches(): array
Gibt SchemaException[] für alle Schemaunterschiede zurück, die beim letzten Merge erkannt wurden (zwischen Snapshots hinzugefügte oder entfernte Spalten).
MergeResult
Merql\Merge\MergeResult ist ein schreibgeschütztes Wertobjekt mit dem Ergebnis des Merges.
$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 ist ein serialisierbares Prüfmodell für einen Merge.
$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>
Verwende MergePlanSerializer::toJson() und ::fromJson() für stabile JSON-Roundtrips.
Selections
ChangeGroupSelection speichert IDs vorgemerkter Änderungsgruppen. Wandle es in ein OperationSelection um, wenn du ausgewählte Merge-Ergebnisse oder Rollback-Pläne erstellst.
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 speichert inverse Operationen und Erwartungen an Abweichungen für eine ausgewählte Anwendung.
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;
$rollback = (new RollbackPlanBuilder())->build(
'rollback-1',
$plan,
$operationSelection,
$liveBeforeRows,
);
$json = RollbackPlanSerializer::toJson($rollback);
Verwende RollbackDrift vor dem Anwenden des Rollbacks und anschließend RollbackPlanApplier, um die inversen Operationen auszuführen.
MergeOperation
Merql\Merge\MergeOperation ist eine einzelne aufgelöste Operation des Merges.
$op->type; // string: 'insert' | 'update' | 'delete'
$op->table; // string
$op->rowKey; // string
$op->values; // array<string, mixed>
$op->source; // string: 'ours' | 'theirs' | 'merged'
Typkonstanten:
MergeOperation::TYPE_INSERT; // 'insert'
MergeOperation::TYPE_UPDATE; // 'update'
MergeOperation::TYPE_DELETE; // 'delete'
Conflict
Merql\Merge\Conflict stellt einen ungelösten Merge-Konflikt dar.
$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
Konflikttypen
| Typ | Bedeutung |
|---|---|
update_update |
Beide Seiten haben dieselbe Spalte auf unterschiedliche Werte geändert |
update_delete |
Unsere Seite hat eine Zeile aktualisiert, die deren Seite gelöscht hat |
delete_update |
Unsere Seite hat eine Zeile gelöscht, die deren Seite aktualisiert hat |
insert_insert |
Beide Seiten haben eine Zeile mit demselben Primärschlüssel eingefügt |
ConflictResolver
Merql\Merge\ConflictResolver wendet eine Auflösungsrichtlinie auf alle Konflikte in einem Merge-Ergebnis an.
resolve(MergeResult $result, ConflictPolicy $policy): MergeResult (statisch)
Gibt ein neues MergeResult zurück, in dem Konflikte gemäß der Richtlinie aufgelöst wurden.
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 ist ein Backed Enum mit drei Fällen.
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 führt die Merge-Logik für einzelne Spalten einer Zeile aus. Wird intern von ThreeWayMerge verwendet.
merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array (statisch)
Gibt ['values' => array, 'conflicts' => list<Conflict>] zurück.
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)
Applier
Merql\Apply\Applier wendet ein Merge-Ergebnis auf eine Datenbank an.
__construct(DatabaseConnection $connection, ?Driver $driver = null)
apply(MergeResult $result, ?Snapshot $base = null): ApplyResult
Führt alle Operationen in einer Transaktion aus. Bei einem Fehler wird ein Rollback ausgeführt und der Fehler in ApplyResult::errors() gemeldet, statt eine Exception auszulösen. Wirft ConflictException, wenn das Ergebnis ungelöste Konflikte enthält.
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());
}
Die Reihenfolge von Fremdschlüsseln wird automatisch berücksichtigt: Einfügungen werden nach Abhängigkeiten sortiert, Löschungen in umgekehrter Abhängigkeitsreihenfolge.
ApplyResult
Merql\Apply\ApplyResult ist ein schreibgeschütztes Wertobjekt, das vom Applier zurückgegeben wird.
$applied->rowsAffected(); // int
$applied->errors(); // list<string>
$applied->hasErrors(); // bool
Nach einem zurückgerollten Apply zählt rowsAffected() weiterhin die Anweisungen, die vor dem Fehler ausgeführt wurden (#4). Prüfe hasErrors(), bevor du dich auf die Anzahl verlässt.
DryRun
Merql\Apply\DryRun erstellt menschenlesbare SQL, ohne etwas auszuführen.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (statisch)
Gibt eine Liste von SQL-Zeichenfolgen zurück, in denen Werte eingefügt sind (nur zur Anzeige).
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 erstellt parametrisierte SQL-Anweisungen. Wird intern von Applier und DryRun verwendet.
generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array (statisch)
Gibt list<array{sql: string, params: list<scalar|null>}> zurück.
use Merql\Apply\SqlGenerator;
$statements = SqlGenerator::generate($result);
foreach ($statements as $stmt) {
$connection->execute($stmt['sql'], $stmt['params']);
}
CellMergeConfig
Merql\CellMerge\CellMergeConfig konfiguriert zellenweise Merge-Strategien für bestimmte Spalten oder Spaltentypen.
auto(): self (statisch)
Praktische Factory, die TEXT/LONGTEXT-Spalten TextCellMerger und JSON-Spalten JsonCellMerger zuweist.
use Merql\CellMerge\CellMergeConfig;
$config = CellMergeConfig::auto();
forColumn(string $column, CellMerger $merger): self
Weist einer bestimmten Spalte einen Merger zu. Verwende table.column für qualifizierte Namen oder einfach column für alle Tabellen.
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
Weist allen Spalten, die einem Typsuchmuster entsprechen, einen Merger zu (Groß-/Kleinschreibung wird bei der Teilzeichenfolgensuche ignoriert).
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
$config = (new CellMergeConfig())
->forType('json', new JsonCellMerger());
getMerger(string $table, string $column, string $columnType = ''): CellMerger
Sucht den Merger für eine Tabelle, Spalte und einen Typ. Priorität: qualifizierter Spaltenname, nicht qualifizierter Spaltenname, Typsuchmuster, Standard (opaque).
CellMerger (Schnittstelle)
Merql\CellMerge\CellMerger ist die Schnittstelle für zellenweise Merge-Strategien.
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;
interface CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
Drei integrierte Implementierungen sind enthalten.
TextCellMerger
Merql\CellMerge\TextCellMerger führt mehrzeiligen Text mit einem dreifachen zeilenweisen Diff zusammen (demselben Algorithmus, den git für Dateiinhalt verwendet). Bearbeiten beide Seiten unterschiedliche Zeilen, gelingt der Merge ohne Konflikt. Bearbeiten beide Seiten dieselbe Zeile unterschiedlich, wird ein Konflikt gemeldet.
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 führt JSON-Werte zusammen, indem Schlüssel der obersten Ebene unabhängig voneinander verglichen werden. Jeder Schlüssel wird mit einer Drei-Wege-Logik zusammengeführt. Verschachtelte Objekte werden als undurchsichtige JSON-Zeichenfolgen verglichen (ohne rekursives Zusammenführen).
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 ist die Standardeinstellung. Behandelt Werte als undurchsichtige Zeichenfolgen und meldet bei Unterschieden immer einen Konflikt.
CellMergeResult
Merql\CellMerge\CellMergeResult ist der Rückgabetyp aller Cell Merger.
$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 ist eine schreibgeschützte Darstellung der Tabellenstruktur.
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>
Treibersystem
merql verwendet ein erweiterbares Treibersystem, um datenbankspezifische Operationen zu abstrahieren. Treiber werden anhand der Verbindungs-Treiberkennung automatisch erkannt.
Driver (Schnittstelle)
Merql\Driver\Driver definiert den Vertrag, den alle Datenbanktreiber implementieren.
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;
}
Integrierte Implementierungen: MysqlDriver und SqliteDriver.
DriverFactory
Merql\Driver\DriverFactory erkennt den Treiber anhand einer Verbindung automatisch.
use Merql\Driver\DriverFactory;
$driver = DriverFactory::create($connection);
Registriere einen benutzerdefinierten Treiber für andere Datenbanken:
DriverFactory::register('pgsql', MyPostgresDriver::class);
Filter
TableFilter
Merql\Filter\TableFilter schließt Tabellen anhand von Glob-Mustern ein oder aus.
use Merql\Filter\TableFilter;
$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
ColumnFilter
Merql\Filter\ColumnFilter entfernt bestimmte Spalten aus Snapshots.
use Merql\Filter\ColumnFilter;
$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
RowFilter
Merql\Filter\RowFilter filtert Zeilen mit einer Prädikatfunktion.
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;
});
Ausnahmen
SnapshotException
Merql\Exceptions\SnapshotException wird ausgelöst, wenn ein Snapshot nicht gefunden wird oder einen ungültigen Namen hat.
SnapshotException::notFound('baseline', '/path/to/baseline.json');
MergeException
Merql\Exceptions\MergeException wird ausgelöst, wenn für einen Merge ein erforderlicher Snapshot fehlt.
MergeException::snapshotRequired('base');
ConflictException
Merql\Exceptions\ConflictException wird ausgelöst, wenn versucht wird, ein Merge-Ergebnis mit ungelösten Konflikten anzuwenden.
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
SchemaException
Merql\Exceptions\SchemaException wird ausgelöst (oder erfasst), wenn sich die Schemas von Snapshots unterscheiden.
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
Schemaabweichungen verhindern den Merge nicht. Sie werden erfasst und können über $result->schemaMismatches() geprüft werden.