ConnectionManager

Zweck des ConnectionManagers ist es, eine Konvention zu definieren, wie die Konfiguration und Implementierung einer Datenbank-Zugriffsschnittstelle gestaltet sein soll. Idealerweise ermöglicht diese Vorgehensweise einen einfachen Austausch einer Treiberschicht gegen eine andere um von einer Datenbank zur anderen zu wechseln.

Trotz, dass dieser Ansatz hinsichtlich der Unterschiede zwischen Datenbanken sehr ambitioniert ist, vereinheitlicht er dennoch die API und schafft ein gemeinsames Verständnis für den Zugriff der Datenschicht einer Applikation auf die Datenbank-Treiberschicht.

1. Konfiguration

Der ConnectionManager fungiert als Factory für konkrete Implementierungen eines Datenbanktreibers. Um den gewünschte Treiber laden zu können, muss dieser zunächst konfiguriert werden. Die geschieht in der Konfigurationsdatei

Code
/APF/config/core/database/{CONTEXT}/{ENVIRONMENT}_connections.ini

Details zu Konfigurationsdateien können dem Kapitel Konfiguration entnommen werden. Die Konfigurationsdatei enthält je eine Sektion für einen Treiber:

APF-Konfiguration
[Sektions-Name] Host = "" User = "" Pass = "" Name = "" [DebugMode = "true|false"]

Der Sektions-Name dient als Referenz für die Erstellung der Treiber-Instanz, Host beinhaltet den Hostnamen des Datenbank-Server, User und Pass die Authentifizierungsdaten und Name benennt die zu verwendende Datenbank. Type dient zur Definition des zur verwendenen Treibers und DebugMode entscheidet, ob der Debug-Modus des Treibers aktiviert werden soll oder nicht.

Der Debug-Modus kann dazu genutzt werden, das aktuell ausgeführte Statement auszugeben. Alternativ dazu kann der optionale Parameter $logStatement der Methoden executeStatement() und executeTextStatement() genutzt werden um das aktuell ausgeführte Statement in eine Log-Datei zu schreiben. Details zur Funktion können der API-Dokumentation entnommen werden.

2. Anwendung

Um den ConnectionManager einsetzen zu können muss zunächst per

PHP-Code
use APF\core\database\ConnectionManager; $cm = &$this->getServiceObject('APF\core\database\ConnectionManager'); $driver = &$cm->getConnection('Sektions-Name');

eine Instanz eines Datenbank-Treibers erzeugt werden.

3. Erweiterung

Gemäß der Definition der abstrakten Klasse AbstractDatabaseHandler können weitere Datenbank-Abstraktions-Klassen hinzugefügt werden. Um diese mit dem ConnectionManager verwenden zu können, müssen diese von der Klasse AbstractDatabaseHandler ableiten bzw. das DatabaseConnection-Interface implementieren.

Möchten Sie einen eigen Datenbank-Treiber implementieren, so nehmen Sie einen der vorhanden Treiber als Vorlage.

4. Vorhandene Treiberschichten

Das Framework liefert drei Treiberschichten mit:

  • SQLite: SQLite-Treiberschicht. Setzt auf die sqlite_*-Funktionen. Hier ist PHP > 5.0.0 notwendig!
  • MySQLi: MySQLi-Treiberschicht. Setzt auf die mysqli_*-Funktionen auf

4.2. SQLite-Treiberschicht

Eine SQLite Treiberschicht kann mit dem Konfigurationseintrag

APF-Konfiguration
[SQLite] Name = "/path/to/my/database.sqlite" Type = "APF\core\database\SQLiteHandler" DebugMode = "true|false"
und den Code-Zeilen
PHP-Code
use APF\core\database\ConnectionManager; $cm = &$this->getServiceObject('APF\core\database\ConnectionManager'); $SQLite = &$cm->getConnection('SQLite');

instanziiert werden. Bitte beachten Sie, dass im Fall von SQLite Host, User und Passwort nicht konfiguriert werden müssen, da SQLite eine integrierte Datenbank-Engine ist.

4.3. MySQLi-Treiberschicht

Der MySQLi-Treiber unterstützt das Ausführen von dynamisch zusammengesetzten Statements und Statements, die in SQL-Dateien abgelegt sind.

Um eine Instanz des MySQLi-Treibers erstellen zu können, ist folgende Konfiguration notwendig:

APF-Konfiguration
[MySQLi] Host = "host" User = "user" Pass = "pass" Name = "name" Type = "APF\core\database\MySQLiHandler" [Port = "3306"] [Socket = "/tmp/mysql.sock"] [DebugMode = "true|false"] [Charset = ""] [Collation = ""]

Mit den Parametern Port und Socket ist es möglich den TCP/IP-Port bzw. den Pfad zum Unix-Socket anzugeben.

Die MySQL-Erweiterung von PHP unterstützte die Übergabe des Ports bzw. Sockets im Hostnamen, sodass ein diese Funktion oft direkt in den Parameter Host eingetragen wurde. Dies funktioniert jedoch bei der MySQLi-Erweiterung von PHP nicht, da hier Port und Socket als gesonderte Parameter übergeben werden müssen. Entsprechend wurden die beiden Parameter hinzugefügt. Der Workaround sollte demzufolge keine Anwendung mehr finden.

Die Parameter Charset und Collation dienen dazu, den Zeichensatz und die Zeichenbehandlung der MySQL-Verbindung zu definieren. Mit dem Parameter Charset werden die MySQL-Variablen

  • character_set_client
  • character_set_connection
  • character_set_results

gesetzt, Collation beeinflusst

  • collation_connection
  • collation_database

Die beiden Parameter sind optional und können bei Bedarf auch wechselseitig gesetzt werden, das Vorhandensein beider ist nicht erforderlich.

Die folgenden Methoden erlauben die Nutzung von Statements mit Bind-Parametern gegen die Datenbank auszuführen:

  • executeTextBindStatement()
  • executeBindStatement()

Mit executeTextBindStatement() kann ein Statement, das - analog zu executeTextStatement() - als String übergeben wurde gegen die Datenbank ausgeführt werden. Als zweiten Parameter erwartet es die Werte der Bind-Parameter.

Die Methode executeBindStatement() führt ein Statement aus, das in einer SQL-Datei abgelegt wurde und reichert dieses mit den im Aufruf übergebenen Parametern an. Da die Implementierung der Bind-Parameter für MySQL-Datenbanken eine genaue Beachtung der Reihenfolge der Bind-Parameter voraussetzt, wurde in der Implementierung eine automatische Re-Sortierung eingeführt, die dafür sorgt, dass die Zuordnung der Parameter des Methoden-Aufrufs zur Definition der Parameter in der SQL-Datei passt.

Für die Nutzung der Methode executeTextBindStatement() steht die Funktion fetchBindResult() zur Verfügung. Diese holt analog zu fetchData() die gewünschte Ergebnismenge ab.

Die folgende Code-Box zeigt Anwendungsbeispiele für den Umgang mit Bind-Statements:

PHP-Code
use APF\core\database\ConnectionManager; $cm = &$this->getServiceObject('APF\core\database\ConnectionManager'); $conn = $cm->getConnection('MySQLi'); // execute textual statement with bind params $data = $conn->executeTextBindStatement( 'SELECT * FROM ent_user_2 WHERE FirstName LIKE ?', array('Christian') ); // execute statement within an sql file with bind params $data = $conn->executeBindStatement( 'VENDOR\module', 'notepad_entries.sql', array('date_from' => '2009-03-20 00:00:00','date_until' => '2010-04-10 00:00:00') );

Die Ausführung der letzten Methode setzt voraus, dass eine SQL-Datei unter

Code
/VENDOR/config/module/{CONTEXT}/{ENVIRONMENT}_notepad_entries.sql

die übergebenen Parameter in der Form

SQL-Statement
SELECT * FROM notepad WHERE save_date BETWEEN [date_from] AND [date_until]

enthält.

4.4. PDO Treiberschicht

Neben den übrigen Treibern lässt sich ebenfalls eine PDO-Treiber Instanz erstellen. Dabei wird ein zusätzlicher Konfigurationsparameter benötigt:

APF-Konfiguration
[MySQL] Host = "host" User = "user" Pass = "pass" Name = "name" Type = "APF\core\database\PDOHandler" PDO = "MySQL" [Port = "3306"] [Socket = "/tmp/mysql.sock"] [DebugMode = "true|false"] [Charset = ""] [Collation = ""]

Mit dem Parameter Type legt man fest, dass der PDO-Treiber verwendet werden soll. Mit dem zusätzlichen Parameter PDO teilt man dem PDO-Treiber mit, welche Treiberschicht dieser verwenden soll.

Mit dem aktuellen Stand wird nur der PDO_MYSQL-Treiber unterstützt, weitere werden allerdings folgen!

Für die Konfiguration des Ports/Sockets, des Zeichensatz und der Collation gelten die selben Bedingungen wie für eine MySQLi-Connection.

Die Besonderheit beim PDO-Treiber sind, dass die execute*()-Methoden ein PDOStatement zurückliefern, dadurch ist es möglich, sowohl die APF-Methoden, als auch die unter PHP.net PDOStatement beschriebenen Methoden zu verwenden.

Bei Verwendung einer execute*()-Methode, wird das PDOStatement direkt ausgeführt, d.h. es können keine PDO spezifischen Platzhalter-Parameter verwenden werden, dafür dient die prepareStatement()-Methode. Außerdem muss die execute-Methode des PDOStatement Objekts nicht mehr aufgerufen werden.

Zusätzlich zu den Methoden aus dem MySQLi-Treiber sind weitere Methoden implementiert.

  • beginTransaction()
  • commit()
  • rollBack()
  • inTransaction()
  • prepareStatement()

Die Methode beginTransaction() startet eine Transaktion (d.h. der autocommit-Modus wird ausgeschaltet), sodass Änderungen an der Datenbank nicht direkt ausgeführt werden. Mit den Methoden commit() und rollBack() kann gesteuert werden, ob die Änderungen übernommen oder verworfen werden.

Mit commit() werden alle Änderungen, die innerhalb einer Transaction durchgeführt wurden, endgültig in die Datenbank geschrieben. Außerdem wird die laufende Transaktion beendet (d.h. autocommit-Modus wird eingeschaltet).

Mit rollBack() werden alle Änderungen innerhalb einer Transaktion verworfen. Außerdem wird die laufende Transaktion beendet (d.h. autocommit-Modus wird eingeschaltet).

Die Methode inTransaction() prüft, ob aktuell eine Transaktion aktiv ist oder nicht.

Die Methode prepareStatement() gibt ein PDOStatement Objekt zurück. Im Statement können Parameter-Platzhalter enthalten sein. Das Statement wird erst ausgeführt, wenn man die execute()-Methode des PDOStatement Objekts aufgerufen wird! Die Platzhalter können per bindParam()-Methode gesetzt werden oder man übergibt der execute()-Methode ein Array als ersten Parameter.

Nachfolgend ein paar Beispiele zur Verwendung des PDO-Treibers:

PHP-Code
use APF\core\database\ConnectionManager; $cm = &$this->getServiceObject('APF\core\database\ConnectionManager'); $conn = $cm->getConnection('PDO'); $statement = $conn->executeTextStatement("SELECT * FROM `ent_user`"); // apf methods while ($row = $conn->fetchData($statement)) { } // or PDO methods while ($row = $statement->fetch()) { } // prepare a statement $statement = $conn->prepare("SELECT * FROM `ent_user` WHERE `FirstName` = :firstname"); // bind params and execute $statement->bindParam(':firstname', 'Christian'); $statement->execute(); // or execute with param array $statement->execute(array(':firstname' => 'Christian'));

5. Statement-Dateien

Jede Implementierung des AbstractDatabaseHandler bringt die Möglichkeit mit, SQL-Statements in Konfigurations-Dateien auszulagern. Vorteil der der Vorgehensweise ist es, dass die in den Statement-Dateien abgelegten Inhalte von mehreren Komponenten genutzt werden können. Dies ist insbesondere im Zusammenhang mit dem Pager interessant, da ein Statement dann sowohl von diesem als auch von einer Daten-Schicht-Komponente genutzt werden kann.

5.1. Konfiguration

Da es sich bei Statement-Dateien - wie bereits angesprochen - ebenfalls um Konfigurations-Dateien handelt, gelten alle Regeln des Konfigurations-Mechanismus. Hierzu bringt das APF den StatementConfigurationProvider und die Konfigurations-Repräsentation StatementConfiguration mit, die in den jeweiligen Treiber-Implementierungen genutzt werden.

Wie für Konfigurations-Dateien üblich besteht der Name einer Statement-Datei aus dem Umgebungs-Präfix, dem Datei-Namen und einer Endung. Im Standard-Setup wird ".sql" als Endung definiert, kann jedoch wie im Kapitel Konfiguration beschrieben für jeden Anwendungsfall angepasst werden. Beispiel:

Code
DEFAULT_load_all_entries_for_category.sql

Der Ablage-Ort des Statements ist ebenfalls wie bei "normalen" Konfigurations-Dateien aus dem Namespace und dem aktuellen Context der Anwendung zusammengesetzt. Ein Statement mit dem Namen load_all_entries_for_category.sql im Namespace VENDOR\widgets\articles wird in der Standard-Konfiguration unter folgendem Pfad erwartet:

Code
/VENDOR/config/widgets/articles/siteone/DEFAULT_load_all_entries_for_category.sql

Der Context der aktuellen Applikation wurde für das Beispiel auf den Wert "siteone" festgelegt.

5.2. Anwendung

Nach dem Anlegen einer Statement-Datei unter dem für die aktuelle Anwendung relevanten Pfad, kann diese über die Methode executeStatement() bzw. executeBindStatement() (nur für MySQLi-Connections) genutzt werden:

PHP-Code
use APF\core\database\ConnectionManager; $conn = $this->getServiceObject('APF\core\database\ConnectionManager') ->getConnection('datastore'); $params = array('somefield' => 'somevalue'); $result = $conn->executeStatement( 'VENDOR\widgets\articles', 'load_all_entries_for_category', $params); // für MySQLi-Verbindungen $data = $conn->executeBindStatement( 'VENDOR\widgets\articlese', 'load_all_entries_for_category', $params);

Das dritte Argument der beiden Methoden definiert jeweils die dynamischen Statement-Parameter, die zur Laufzeit an den Treiber übergeben werden können. Innerhalb eines Statements werden diese durch Notation in eckigen Klammern definiert. Soll der Parameter somefield innerhalb des Statements mit dem dynamisch übergebenen Wert gefüllt werden, so ist dieser im Statement wie folgt zu definieren:

SQL-Statement
SELECT somefield, anotherfield FROM mytable WHERE somefield = '[somefield]';

Pro Statement können beliebig viele dynamische Parameter an beliebigen Stellen definiert werden. Diese können einen Wert oder auch dynamische Teile eines Statements enthalten.

Ein weiteres Anwendungs-Beispiel ist unter Kommentar-Funktion beschrieben.

6. Erzeugung von Datenbank-Verbindungen mit dem DI-Container

Neben der in Kapitel 2 beschriebenen Art der Erzeugung von Datenbank-Verbindungen über den ConnectionManager können Sie auch den DI-Container des APF nutzen. Die folgenden Kapitel beschreiben die Nutzung des DIServiceManager um Datenbank-Verbindungen als Service zu erzeugen und zu verwenden.

6.1. Konfiguration

Um Services bzw. Objekte mit dem DIServiceManager erzeugen zu können, müssen diese zunächst durch eine Konfiguration beschrieben werden. Eine Datenbank-Verbindung lässt sich wie auch andere Services durch eine Konfigurations-Sektion mit den gewünschten Inhalten erstellen. Um eine Instanz des MySQLiHandler zu beziehen ist folgende Definition notwendig:

APF-Konfiguration
[mysql-connection] class = "APF\core\database\MySQLiHandler" servicetype = "SINGLETON" setupmethod = "setup" conf.host.method = "setHost" conf.host.value = "localhost" conf.name.method = "setDatabaseName" conf.name.value = "products" conf.user.method = "setUser" conf.user.value = "root" conf.pass.method = "setPass" conf.pass.value = "..." conf.charset.method = "setCharset" conf.charset.value = "utf8" conf.collation.method = "setCollation" conf.collation.value = "utf8_general_ci"
Zur Konfiguration können Sie alle in der Klasse MySQLiHandler vorhandenen Setter nutzen. Möchten Sie beispielsweise den Debug-Modus aktivieren (Details siehe Kapitel 1) und den zu nutzenden LogWriter ändern (Details siehe Logger), können Sie für jede Option einfach eine weitere Gruppe hinzufügen. Beispiel:
APF-Konfiguration
conf.debug.method = "setDebug" conf.debug.value = "true" conf.log-target.method = "setLogTarget" conf.log-target.value = "..."
Bitte achten Sie darauf, die Datenbank-Verbindung mit der Methode setup() zu initialisieren. Andernfalls wird die Instanz der Klasse MySQLiHandler zwar erzeugt, die Verbindung zur Datenbank jedoch nicht aufgebaut. Details zur Nutzung des Attributs setupmethod können Sie im Kapitel Services nachlesen.

Die beschriebene Art der Konfiguration lässt sich auf alle mit dem APF mitgelieferten Datenbank-Treiber anwenden.

6.2. Anwendung

Die in Kapitel 6.1 definierte Datenbank-Verbindung können Sie nun beispielsweise in einem Controller über den DIServiceManager beziehen. Hierzu lässt sich folgender Code nutzen:

PHP-Code
use APF\core\database\MySQLiHandler; use APF\core\pagecontroller\BaseDocumentController; class DoSomethingController extends BaseDocumentController { public function transformContent() { /* @var $conn MySQLiHandler */ $conn = $this->getDIServiceObject('VENDOR\..\data', 'mysql-connection'); $conn->executeTextStatement('SELECT * FROM ...'); ... } }

Der im vorangegangenen Kapitel definierte Service lässt sich nun nicht nur direkt verwenden, sondern kann auch zur Initialisierung weiterer Services genutzt werden. Details hierzu entnehmen Sie bitte Kapitel 4.4.4 der DIServiceManager-Dokumentation.

Kommentare

Möchten Sie den Artikel eine Anmerkung hinzufügen, oder haben Sie ergänzende Hinweise? Dann können Sie diese hier einfügen. Die bereits verfassten Anmerkungen und Kommentare finden Sie in der untenstehenden Liste.
Für diesen Artikel liegen aktuell keine Kommentare vor.