Lorem Ipsum

Integration von Doctrine in eine modulbasierte Zend Anwendung

Eines der erste Dinge, die mir als Einsteiger in das Zend Framework auffielen, war die Vernachlässigung des M in Zends MVC Implementation. Views und Controller, wenngleich sie erst nach einer kleinen Einarbeitungszeit wirklich verständlich werden, bieten genau das, was eine komplexe Webanwendung benötigt. Jedoch fehlt eine wirklich umfangreiche Implementation von Models in Zend. Auch wenn diese im Moment in Arbeit ist, haben wir uns deshalb für Doctrine entschieden, welches genau diese Lücke für uns füllen soll. Es ist jedoch eine Herausforderung für sich, Doctrine in seine Zend Anwendung zu integrieren, zumal wir eine modulbasierte Struktur zugrunde liegen haben.

Doctrine und Module


Die folgende Anleitung soll eine Möglichkeit zeigen, wie sich Doctrine und eine modulbasierte Zend Applikation verbinden lassen. Da Tutorials meist nur die simple Integration von Doctrine und Zend in einer „einfachen“ Anwendung ohne Module zeigen, möchte ich hier besonders auf Module eingehen. Natürlich könnte man auch, unabhängig von Modulen, die Models einfach in einen gemeinsamen Ordner unterbringen. Dadurch geht jedoch ein zentraler Aspekt des modularen Aufbaus verloren und wir haben uns deshalb um eine andere Lösung bemüht. Des Weiteren muss angemerkt werden, dass in unserem Fall die Models eines Modules auch Beziehungen zu Models anderer Module haben können. Eine unabhängige Behandlung ist daher nicht möglich. Wäre dies nicht der Fall, könnte man auch einfach jedes Modul als eigenständige Anwendung betrachten und die oben erwähnten Tutorials zu Rate ziehen.
Im Zuge der Anleitung werde ich auch gleich auf einige Vorzüge von Doctrine eingehen, empfehle jedoch die Doctrine-Einführung durchzuarbeiten.

Doctrine’s YAML Schema

Um seine Models von Doctrine generieren zu lassen, bietet sich das Erstellen einer YAML-Schema-Datei an. Diese kann entweder per Hand geschrieben oder aus einer existierenden Datenbank generiert werden:
[cc escaped=“true“ lang=“php“]
Doctrine_Manager::connection(„mysql://user:pw@localhost/dbname“);
Doctrine::generateYamlFromDb(’schema.yml‘);
[/cc]
Anschließend sollte das Schema jedoch nochmals überarbeitet werden, um eventuelle Anpassungen wie Klassennamen, Beziehungen, etc. durchzuführen. Es empfiehlt sich das Erstellen eines PHP Scripts für alle Doctrine Operationen. Auf die Erstellung eines solchen Skripts möchte ich jedoch hier nicht weiter eingehen, da dies bereits in der Doctrine Einführung und in diversen Tutorials ausführlich behandelt wird.

Ist das YAML-Schema erst einmal ausgearbeitet, lässt sich später auch die Datenbank daraus generieren. Änderungen im Datenbankdesign können also fortan direkt im YAML-Schema erfolgen, unabhängig von dem verwendeten Datenbanksystem.
Aus der YAML-Datei lässt sich jedoch auch das Doctrine Model generieren, welches wir in unserer Webanwendung verwenden wollen. Hierfür müssen wir einige Einstellungen vornehmen, die Doctrine bei der Generierung berücksichtigen soll:
[cc escaped=“true“ lang=“php“]
$options = array(
’suffix‘ => ‚.php‘,
‚baseClassPrefix‘ => ‚Orm_‘,
‚pearStyle‘ => true,
‚baseClassesDirectory‘ => “,
‚generateTableClasses‘ => true
);
Doctrine::generateModelsFromYaml(’schema.yml‘, ‚models‘, $options);
[/cc]
Der Eintrag [ccie_PHP]’baseClassPrefix‘ => ‚Orm_'[/ccie_PHP] erweitert den Klassennamen unserer Model-Basisklassen und erzeugt durch [ccie_PHP]’pearStyle‘ => true[/ccie_PHP] die Model-Datei im Ordner ‚Orm‘ ohne jedoch Orm_ im Dateinamen – eben im PEAR-Namensstil. Durch [ccie_PHP]’baseClassesDirectory‘ => “[/ccie_PHP] verzichten wir auf das ‚generated‘ Verzeichnis und mittels [ccie_PHP]’generateTableClasses'[/ccie_PHP] werden zusätzlich Klassen, abgeleitet von Doctrine_Table, erzeugt.
Nehmen wir an, in unserer Datenbank bzw. im YAML-Schema befinden sich die Tabellen „user“ und „appointment“. Der obige Code würde dann folgendes Erzeugen:
[ccN]
models\Orm\Appointment.php class Orm_Appointment extends Doctrine_Record
models\Orm\User.php class Orm_User extends Doctrine_Record
models\Appointment.php class Appointment extends Orm_Appointment
models\AppointmentTable.php class AppointmentTable extends Doctrine_Table
models\User.php class User extends Orm_User
models\UserTable.php class UserTable extends Doctrine_Table
[/ccN]
Unsere Webanwendung ist in folgender (etwas vereinfachter) Verzeichnisstruktur aufgebaut, wobei die eben aufgelisteten Dateien im Ordner /doctrine generiert werden:

Nun kommt ein etwas unschöner Vorgang, denn wir müssen die eben generierten Dateien in die Model-Ordner unserer Module verschieben. Um beim obigen Beispiel zu bleiben, verschieben wir User.php und UserTable.php in das ‚Core‘ Modul, also etwa modules/core/models/. Appointment.php und AppointmentTable.php verschieben wir nach modules/calendar/models/. Und den Orm Ordner können wir als Ganzes in unserem Application-Verzeichnis platzieren (wichtig ist nur, dass der Zend Autoloader die Dateien findet, mehr dazu gleich).

Autoloader

Da wir nun erfolgreich unsere Models generiert haben, können wir unserer Webapplikation mitteilen, wo diese zu finden sind. Folgende Methode kann zur Initialisierung von Doctrine in der Bootstrap Klasse (application/bootstrap.php) platziert werden:
[cc escaped=“true“ lang=“php“]
public function _initDoctrine()
{
$this->getApplication()->getAutoloader()
->registerNamespace(‚Doctrine‘)
->registerNamespace(‚Orm‘)
->pushAutoloader(array(‚Doctrine‘, ‚autoload‘))
->pushAutoloader(array(‚Doctrine‘, ‚modelsAutoload‘));

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE)
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true)
$manager->connection(„mysql://user:pw@localhost/dbname“, „conn“);

Doctrine::loadModels(array( APPLICATION_PATH . ‚/modules/core/models‘,
APPLICATION_PATH . ‚/modules/calendar/models‘));
}
[/cc]
[ccie_PHP]registerNamespace(‚Doctrine‘)[/ccie_PHP] sorgt dafür, dass die Doctrine-Klasse in Doctrine.php geladen werden kann, vorausgesetzt der Pfad zur Doctrine-Library wurde als Include-Pfad, zum Beispiel in der index.php, gesetzt. Sollte Doctrine.php direkt inkludiert worden sein, kann man auf diese Zeile auch verzichten. [ccie_PHP]registerNamespace(‚Orm‘)[/ccie_PHP] ermöglicht das Autoloading der zuvor generierten Basisklassen Orm_User und Orm_Appointment. Der Ordner Orm muss dabei in einem inkludierten Pfad liegen, sodass er dem Zend Autoloader bekannt ist. Mittels den beiden pushAutoloader() Aufrufen registrieren wir die autoload() und modelsAutoload() Callbacks von Doctrine. Erstere ist für die Doctrine-Klassen zuständig, zweitere für unsere Models. Nun konfigurieren wir den Doctrine_Manager, sodass dieser unsere Models bei Bedarf (Conservative) und unsere Tables (UserTable, AppointmentTable) lädt. Mittels [ccie_PHP]Doctrine::loadModels()[/ccie_PHP] teilen wir Doctrine die Pfade mit, in denen sich die Model-Dateien befinden. In der Praxis kann der Aufruf natürlich auch entweder in den speziellen Modul-Bootstrap-Klassen erfolgen, oder eine Methode verwendet werden, welche alle Model-Pfade der Module zurückgibt.
Nun können wir die erstellten Model Klassen beliebig um eigene Methoden erweitern und in unseren Controller_Actions und Views verwenden:
[cc escaped=“true“ lang=“php“]
// class User extends Doctrine_Record
$user = new User();
$user->username = „Admin“;
$user->myMethod(); // eigene Methode in User Klasse
$user->save();

// UserTable
$usertable = Doctrine::getTable(„User“);
$usertable->myTableMethod(); // eigene Methode in UserTable Klasse
[/cc]
Falls sich das Schema ändert, sollte in den meisten Fällen ein Updaten des Orm Ordners ausreichen, dessen Dateien wir ohnehin nicht verändern wollen.

Schlusswort

Ob diese Variante, Doctrine in die eigene Zend Anwendung zu integrieren geeignet ist, hängt stark von den Anforderungen ab. Wir haben uns für diese Methode entschlossen, da wir zwar Abhängigkeiten zwischen den Models der Module haben, jedoch trotzdem eine prinzipielle Trennung vornehmen wollten. Jeder Teilbereich unseres Datenbankschemas wird in einer eigenen YAML-Schema-Datei definiert (also etwa core.yml und calendar.yml). Man könnte auch auf Packages (siehe Doctrine’s YAML Beschreibung) zurückgreifen, jedoch ändert sich dadurch die Namensgebung und eine weitere abstrakte Klasse wird für die Models erstellt. Der Aufwand die generierten Model-Dateien in die entsprechenden Ordner zu verschieben hält sich jedoch in Grenzen, weswegen wir uns für diese Variante entschieden haben.

Verwendete Versionen:
Zend Framework 1.10.8
Doctrine 1.2





Anfrage

Wir freuen uns auf Ihre Herausforderung.

0316 22 88 42

8:00 - 16:30