PHP Datenbankabstraktion mit Doctrine
Die PHP Datenbankabstraktion mit Doctrine bietet jede Menge Vorteile auf die ein modernes PHP Projekt nicht verzichten sollte. Ich zeige euch wie man Doctrine aufsetzt und in einem Projekt sinnvoll einsetzt.
PHP Datenbankabstraktion mit Doctrine
Immer wenn man ein neues Projekt von Grund auf beginnt sollte man sich schon früh Gedanken über die Anbindung an die Datenbank machen. In meinem Racing Manager Projekt habe ich darauf verzichtet und alle SQL Statements über die PDO Schnittstelle per Hand in einer einzigen Database Klasse erstellt, wobei diese Klasse nur als Bindeglied zur Datenbank dienen soll und keine Programmlogik enthalten soll. Das ging vor allem zu Beginn sehr gut, hat sich aber im Nachhinein als umständlich erwiesen. In einer mehrere tausend Zeilen große Datei findet man alle Selects, Inserts und Updates. Das macht die Sache schwer wart- und erweiterbar. Es kam zu folgenden negativen Effekten:
- doppelte Funktionen
- verschachtelte Funktionen mit Programmcode
- chaotische Gliederung
Damit das Projekt besser strukturiert ist empfehlen sich Datenbank Abstraktionslayer. Diese sollen nur Logik für die Datenspeicherung beziehungsweise Abfrage von Daten zur Datenbank enthalten. Objektorientierung ist gerade bei modernen Projekten ein Muss und zusätzlich sollte der Layer das Datenbanksystem dahinter komplett austauschbar machen (also egal ob MySQL, MSSQL, Oracle, oder ähnliches).
Installation
Eines der führenden Systeme im Open Source Sektor bei PHP ist dafür Doctrine.
Installation Composer
Die Installation von Composer funktioniert wie folgt:
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('SHA384', 'composer-setup.php') === 'e115a8dc7871f15d853148a7fbac7da27d6c0030b848d9b3dc09e2a0388afed865e6a3d6b3c0fad45c48e2b5fc1196ae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" sudo mkdir /usr/local/bin/composer sudo php composer-setup.php --install-dir=/usr/local/bin/composer rm composer-setup.php
Composer wird zwingend benötigt, damit man Doctrine installieren kann. Ich habe das auf einem virtuellen Server gemacht. Damit hatte ich root Rechte und die Installation hat problemlos geklappt. Man beachte: composer wird in einen Systemordner kopiert. Hat man die Rechte nicht muss man auf ein anderes Verzeichnis ausweichen. Wichtig ist nur, dass composer danach ausgeführt werden kann. Sollte man composer auf einem Shared Host gar nicht installieren können braucht man trotzdem nicht auf Doctrine verzichten. Man kann die folgende Doctrine Installation auch auf einem virtuellen Server mit root rechten erledigen und das Projektverzeichnis samt Doctrine dann unabhängig von Composer auf den Webspace kopieren.
Installation Doctrine
Man erstellt eine neue Datei composer.json im Projektverzeichnis. Es hat folgenden Inhalt:
{ "require": { "doctrine/orm":"*" } }
Ausgeführt wird das dann mit folgenden Befehl:
/usr/local/bin/composer/composer.phar install
Es wird nun im aktuellen Ordner ein Unterverzeichnis vendor erstellt in dem der Source Code von Doctrine liegt. Diesen muss man nun ins eigene Projekt integrieren, am besten über eine bootstrap.php Datei in der allgemeine Einstellungen für das neue Projekt festgelegt werden. Das geht so:
<?php // bootstrap.php // Include Composer Autoload (relative to project root). require_once "vendor/autoload.php";
Der Source Code und die Dokumentation ist auch auf der Webseite von Doctrine beschrieben.
Konfiguration
Wie bereits zur Installation kurz erwähnt sollte man Doctrine in einer bootstrap.php Datei initialisieren. Meine bootstrap.php meines Demoprojekts sieht folgendermaßen aus:
// Doctrine2 Stuff // Include Composer Autoload (relative to project root). require_once "vendor/autoload.php"; use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\EntityManager; $paths = array("entities"); // the connection configuration $dbParams = array( 'driver' => 'pdo_mysql', 'user' => 'BENUTZER', // DB User 'password' => 'PASSWORT', // DB Password 'dbname' => 'DATENBANK', // DB Name ); $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); $config->setMetadataDriverImpl( new Doctrine\ORM\Mapping\Driver\AnnotationDriver( new Doctrine\Common\Annotations\CachedReader( new Doctrine\Common\Annotations\AnnotationReader(), new Doctrine\Common\Cache\ArrayCache() ), $paths ) ); $entityManager = EntityManager::create($dbParams, $config);
In dieser Datei werden mit DATENBANK, BENUTZER und PASSWORT Datenbank Verbindungsdaten angegeben. Als Treiber verwenden wir pdo_mysql, das müsste man an die verwendete Datenbank angepasst werden. Mir diesen Schritten ist Doctrine für das Projekt initialisiert und man kann erstellte Entities verwenden.
Entities erstellen
Doctrine ist dafür gemacht worden, dass die Datenbank abstrahiert wird. Das heißt als Entwickler braucht man sich keine Gedanken über die dahinter liegende Datenbank machen. Generell gibt es zwei Ansätze wie man mit Doctrine arbeitet:
- Tabellen automatisch erstellen
Der Entwickler kann nur PHP und er erstellt für alle Models die in der Datenbank gespeichert werden sollen entsprechende Entites. In der Doctrine Dokumentation steht wie man das macht. Es geht darum in der Klasse Kommentare in einer bestimmten schreibweise hinzuzufügen. Durch diese Informationen kann Doctrine die Daten der Klasse in die Datenbank kopieren. Das ist offenbar der meist genutzte Ansatz. - Models automatisch erstellen
Aus meiner Erfahrung der viel wichtigere Ansatz ist aber aus einem zuvor bekannten Datenbankmodell automatisiert Entites, also PHP Klassen zu erstellen. Das will ich in den nächsten Abschnitt an einem Beispiel zeigen.
Ich habe ein Datenbank mit der Tabelle Message erstellt. Message enthält neben einer Id mit Autowert noch ein varchar für die IP Adresse und ein datetime Feld für den Timestamp. Die Entities für alle Tabellen einer Datenbank und die dazugehörigen Getter uns Setter Methoden können mit folgenden zwei Befehlen automatisch erstellt werden:
#erstellt Entities aus den Datenbanktabellen ./vendor/bin/doctrine orm:convert-mapping -f --from-database annotation entities/ #erstellt zu den Entities passende Setter und Getter Methoden ./vendor/bin/doctrine orm:generate-entities --generate-annotations="true" entities/
Die Entity Klassen werden im Ordner entities erstellt.
Test
Die Entities wurden erstellt. Nun zeige ich noch an einem kleinen Skript wie man mit Doctrine und den daraus erstellten Entities Abfragen macht. Die index.php Datei meines Projekts sieht folgendermaßen aus:
<?php include "bootstrap.php"; //use Entity\Message; require_once 'entities/Message.php'; //store to db $msg = new Message(); $msg->setText($_SERVER['REMOTE_ADDR']); $datetime = new DateTime(date('Y-m-d H:i:s', time())); $msg->setPostedAt($datetime); // tells Doctrine you want to (eventually) save the Product (no queries yet) $entityManager->persist($msg); // actually executes the queries (i.e. the INSERT query) $entityManager->flush(); //get from db $query = $entityManager->createQuery( 'SELECT m FROM Message m' ); $msgs = $query->getResult(); var_dump($msgs);
Zuerst wird die bootstrap.php Datei mit der Doctrine Initialisierung eingebunden. Außerdem müssen wir alle verwendeten Entites einbinden, in dem Fall nur die Message.
Im ersten Teil erstellen wir uns ein neues Message Objekt, setzen die Werte für den Text und den Zeitstempel und speichern dieses ab. Nach dem flush() Aufruf steht der Wert in der Datenbank. Gratulation, du hast gerade die IP Adresse des Aufrufs von index.php in der Datenbank geloggt.
Im zweiten Teil zeige ich noch wie man Daten aus der Datenbank abfragt. Dazu erstellt man eine Query mit einem Select Statement. Als Resultset bekommt man das Message Objekt beziehungsweise ein Array aus den Ergebnissen. Ich habe das einfach ausgegeben.
Fazit
Es ist nicht ganz einfach Doctrine zum Laufen zu bringen. Als PHP Anfänger kann das schon recht kompliziert sein. Läuft es aber einmal, dann nimmt es sehr viel arbeit ab. Die Kommunikation mit der Datenbank ist zu fast 100% gekapselt und man braucht sich kaum noch Gedanken machen. Das Datenbanksystem dahinter ist nun ganz einfach austauschbar. Außerdem ist der objektorientierte Ansatz für eine spätere Weiterentwicklung oder Wartung gegenüber gewachsener System rein auf PDO ein großer Vorteil.
Was denkt ihr zu Doctrine? Gibt es vernünftige Alternativen?
„und zusätzlich sollte der Layer das Datenbanksystem dahinter komplett austauschbar machen (also egal ob MySQL, MSSQL, Oracle, oder ähnliches)“
Warum? Ich entscheide mich schließlich von vornherein auf ein bestimmtes DBMS, weil dieses genau die Funktionen/Features bietet die ich brauche. Sollte ich später doch noch auf ein anderes umsteigen wollen, wäre es mir lieber, alle Queries neu zubauen als von vonherein auf spezifische Featuers zu verzichten. Wenn ich mich z.B. für MySQL entscheide aufgrund der Menge an verschiedenen Datentypen und der jeweiligen Speicherung, wäre es ineffizient, Doctrine zu benutzen, das nur eine Handvoll Datentypen verarbeiten kann.
Im privaten Umfeld ja, aber wenn die IT Abteilung die Lizenz für das DB System XY aus Kostengründen nicht verlängert bist du froh, wenn der Switch aufgrund kluger Implementierung weniger als einen Tag Arbeit bedeutet.
schön geschrieben!