Photo by Aron Visuals on Unsplash

Versionsverwaltung mit Trigger in MySQL

Beitragsfoto von Aron Visuals bei Unsplash.

Versionsverwaltung und Versionierung in Symfony und Doctrine auf Basis von SQL-Trigger: Unser NetBrothers VersionBundle versioniert Einträge in der MySQL-Datenbank on the fly.

Mit unserem Umstieg vom Zend-Framework zu Symfony und vom ORM Propel zu Doctrine standen wir vor zwei Problemen: Das ORM Propel hatte die Versionierung on Board. In den bestehenden Projekten waren also Grundfeste der Versionsverwaltung implementiert. Und als Entwickler hatten wir uns an den Komfort gewöhnt, dass die Versionierung im Hintergrund läuft. Wie gehen wir also mit unseren bisherigen Projekten um? Und wie wollen wir zukünftig verfahren?

Kurzerhand haben wir auf Basis der Versionierung in Propel ein Bundle für Symfony programmiert – unser NetBrothers VersionBundle.

Das Konzept

Das VersionBundle manipuliert die Datenbank: Grundlage sind alle Tabellen mit einer Spalte Namens version. Für dies Originaltabellen werden Versionstabellen angelegt – sie besitzen die gleichen Spalten wie die Originaltabellen. Anschließend werden in jeder Originaltabelle SQL-Trigger angelegt, die sowohl bei einem Insert als auch bei einem Update eine Kopie des betroffenen Datensatzes in der Versionstabelle erstellen.

Die Vorteile dieses Konzepts liegen auf der Hand:

  1. Die Programmierer müssen sich nicht aktiv um die Versionsverwaltung kümmern. Es genügt, einmalig bei der Erstellung der Entitäten auf die Versionsverwaltung zu achten.
  2. Die Versionsverwaltung geschieht direkt in der MySQL-Datenbank. Dies entlastet deutlich die PHP-Engine.
  3. Erfolgen Manipulationen direkt in MySQL auf einer Originaltabelle, wird ebenfalls eine Version erstellt.

Installation und Setup

Das NetBrothers VersionBundle ist bei GitHub veröffentlicht. Hier kannst Du den Code einsehen.
Dank Packagist kannst Du das Bundle via composer in Dein Symfony-Projekt integrieren. Folge einfach der Anleitung aus der README.

Wichtig ist, dass Du Die Konfiguration des VersionBundle in den Konfigurationspfad Deines Symfony-Projektes kopierst. In der Grundeinstellung wird die Tabelle ignoriert, in der Doctrine durchgeführte Migrationen speichert. Ferner werden die Spalten id, version, created_at und updated_at beim Vergleich von Originaltabelle und Versionstabelle ignoriert.

Dann musst Du unbedingt Doctrine mitteilen, dass es bei Migrationen die Versionstabellen ignoriert. Dies geschieht über den Konfigurationsschlüssel schema_filter in der Datei doctrine.yaml. Ein Beispiel hierfür findest Du im VersionBundle in der Datei doctrine_example.yaml. Tust Du das nicht, wird Doctrine bei einer zukünftigen Migration die Versionstabellen löschen.

Verwendung

Einbindung des Traits VersionColumn in Entität

Die Verwendung ist kinderleicht. Zuerst sorgst Du dafür, dass Deine Originaltabellen eine Spalte version besitzen.

Binde doch dafür in Deiner Entität das im Bundle existierende Trait VersionColumn ein, wie im Bild abgebildet.

Anschließend migrierst Du die Datenbank, und Deine Tabellen besitzen eine Spalte version.

Jetzt öffnest Du Deine Konsole, wechselst in das Symfony-Projektverzeichnis und führst folgenden Befehl aus:

php bin/console netbrothers:version

Anschließend sollten die Versionstabellen existieren und in den Originaltabellen jeweils vier neue Trigger erstellt sein.

Welche Optionen es gibt und was diese bedeuten, kannst Du in der README nachlesen.

Migrationen

Immer wieder müssen Änderungen an der Datenbankstruktur vorgenommen werden. Bei existierenden Versionstabellen musst Du selbst dafür sorgen, dass die neuen Spalten der Originaltabelle auch in der Versionstabelle existieren. Dies stellst Du selbst in Deinem Migrationsskript sicher. Achte dabei auf eine identische Reihenfolge. Erstelle dann die Trigger neu mit:

php bin/console netbrothers:version --create-trigger

Blick unter die Haube

Der Workflow ist immer gleich:

  1. Zunächst wird analysiert, welche Tabellen eine Spalte version besitzen.
  2. Für jede dieser Originaltabellen wird geprüft, ob es eine Versionstabelle gibt.
    • Gibt es keine Versionstabelle, wird diese samt benötigter Trigger in der Originaltabelle erstellt.
    • Existiert eine Versionstabelle, werden die Spalten der Originaltabelle mit den Spalten in der Versionstabelle verglichen: Haben die Spalten den selben Typ? Die selbe Default Value? Die selbe NotNull-Einstellung? Stimmt der Vergleich nicht, wird eine Fehler geworfen. Andernfalls – und das sollte der Standard sein – werden die existierenden Trigger gelöscht und neue Trigger erstellt.
  3. Für jede Versionstabelle wird geprüft, ob es eine Originaltabelle gibt. Falls nicht, werden gegebenenfalls existierende Trigger gelöscht.

Screenshots

netbrothers_version.yaml
doctrine.yaml
Ausgabe der SQL-Befehle
Zusammenfassung
Erfolg: Versionen und Trigger
Ausgabe eines Fehlers - NetBrothers VersionBundle
Ausgabe eines Fehlers – NetBrothers VersionBundle: Die Originaltabelle besitzt eine Spalte, die in der Versionstabelle nicht existiert.