Zum Inhalt springen

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.

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

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.

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

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

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

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

PHP
$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().

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

PHP
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

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

Erstellt eine SQLite-Verbindung. Aktiviert Fremdschlüssel automatisch über PRAGMA foreign_keys = ON.

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

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

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() 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.

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

Erfasse physische Tabellen unter kanonischen Tabellennamen. Verwende dies, wenn dieselbe logische Tabelle in verschiedenen Umgebungen unterschiedliche physische Namen hat.

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

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']),
]);

Snapshot

Merql\Snapshot\Snapshot ist ein schreibgeschütztes Wertobjekt, das einen vollständigen Datenbankzustand darstellt.

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 enthält die Zeilen, Fingerprints und das Schema einer Tabelle.

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 sind Eingabedaten für 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 definiert die Spalten, aus denen Zeilenschlüssel erstellt werden.

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 (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.

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

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

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

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

Schreibgeschützte Wertobjekte, die einzelne Zeilenänderungen darstellen.

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 stellt eine einzelne Spaltenänderung innerhalb einer Zeilenaktualisierung dar.

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

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

Führt drei Wege zusammen. Berechnet intern Änderungssätze (Basis zu unseren Änderungen, Basis zu deren Änderungen) und führt sie zusammen.

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

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 ist ein serialisierbares Prüfmodell für einen Merge.

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>

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.

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 speichert inverse Operationen und Erwartungen an Abweichungen für eine ausgewählte Anwendung.

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

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

Typkonstanten:

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

Conflict

Merql\Merge\Conflict stellt einen ungelösten Merge-Konflikt dar.

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

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.

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 ist ein Backed Enum mit drei Fällen.

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 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.

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)

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.

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());
}

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.

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

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 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.

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

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

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

Weist allen Spalten, die einem Typsuchmuster entsprechen, einen Merger zu (Groß-/Kleinschreibung wird bei der Teilzeichenfolgensuche ignoriert).

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

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

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 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).

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 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.

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 ist eine schreibgeschützte Darstellung der Tabellenstruktur.

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>

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.

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;
}

Integrierte Implementierungen: MysqlDriver und SqliteDriver.

DriverFactory

Merql\Driver\DriverFactory erkennt den Treiber anhand einer Verbindung automatisch.

PHP
use Merql\Driver\DriverFactory;

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

Registriere einen benutzerdefinierten Treiber für andere Datenbanken:

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

Filter

TableFilter

Merql\Filter\TableFilter schließt Tabellen anhand von Glob-Mustern ein oder aus.

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

PHP
use Merql\Filter\ColumnFilter;

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

RowFilter

Merql\Filter\RowFilter filtert Zeilen mit einer Prädikatfunktion.

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;
});

Ausnahmen

SnapshotException

Merql\Exceptions\SnapshotException wird ausgelöst, wenn ein Snapshot nicht gefunden wird oder einen ungültigen Namen hat.

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

MergeException

Merql\Exceptions\MergeException wird ausgelöst, wenn für einen Merge ein erforderlicher Snapshot fehlt.

PHP
MergeException::snapshotRequired('base');

ConflictException

Merql\Exceptions\ConflictException wird ausgelöst, wenn versucht wird, ein Merge-Ergebnis mit ungelösten Konflikten anzuwenden.

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

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