Ein Tutorial zum Einstieg in das PHP-Framework: Anwendungsentwicklung mit Symfony
Eine Symfony-Anwendung ist ein Projekt, das sich in Applikationen und Module aufteilt. Alle diese Einzelkomponenten lassen sich komfortabel mit dem Symfony-Kommando erzeugen. Eine typische Webanwendung besteht aus einer Frontend- und einer Backend-Applikation, die jeweils diverse Module enthalten können. Um die Architektur des Symfony-Frameworks anschaulich darzustellen,
werden wir als Beispielanwendung ein Blog implementieren. Der Einfachheit halber gehen wir hier von der Sandbox-Variante von Symfony [1] aus, in der schon ein Projekt mit einer Frontend-Anwendung vorinstalliert ist. Die Sandbox wird einfach im Webserver-Root-Verzeichnis entpackt. Da wir die Blogeinträge in einer Datenbank ablegen wollen, sollte eine MySQL-Datenbank vorhanden sein. Wir verwenden in diesem Tutorial eine Datenbank mit dem Namen „blog“. Username und Passwort sind ebenfalls „blog“. Die Datenbank legt man entweder über die Kommandozeile oder komfortabel mit phpMyAdmin an.
Das Datenmodell
Basis eines jeden datenbankbasierten Web-Projekts ist ein sauberes Datenmodell. Ein solches relationales Schema erstellt man üblicherweise im Texteditor oder mit einem visuellen Modellierungswerkzeug wie zum Beispiel DBDesigner 4 [2]. Die neben den eigentlichen Relationen im Schema enthaltenen Fremdschlüsselbeziehungen zwischen den Tabellen verwendet Symfony später intelligent in seinem Datenmodell. Das von Symfony genutzte Propel-XML für das DB-Schema ist unabhängig vom verwendeten Datenbanksystem, die erstellte Anwendung kann also problemlos auf jedes unterstützte DBMS portiert werden. Sollte im config-Verzeichnis bereits eine schema.yml-Datei liegen, so ist diese zu löschen, da wir auf die wesentlich komfortablere XML-Schemadatei setzen. Mit einem guten Editor (z. B. Eclipse) kann sie dank vorhandener DTD (liegt bei den Propel-Quellen) mit Code-Completion und Validierung effizient und fehlerfrei erstellt werden.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE database SYSTEM "../lib/symfony/vendor/propel-generator/resources/dtd/database.dtd" > <database defaultIdMethod="native" name="blog"> <table name="blog_entry"> <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true" /> <column name="title" type="VARCHAR" size="255" required="true" /> <column name="content" type="VARCHAR" size="2048" /> <column name="deleted" type="BOOLEAN" default="0" /> <column name="created_at" type="TIMESTAMP"/> <column name="updated_at" type="TIMESTAMP" /> </table> </database>
Listing 1
Aus diesen Informationen lassen wir den Codegenerator von Symfony nun ein Objektmodell erzeugen. Damit Symfony auch direkt eine Datenbank anlegen kann, muss diese zuerst in den Dateien databases.yml und propel.ini konfiguriert werden. Während in propel.ini die Werte für den ORM-Layer verwaltet werden, regelt die databases.yml den Datenbankzugriff für die eigentliche Symfony-Anwendung. In den meisten Fällen stehen in beiden Dateien die gleichen Parameter.
propel.targetPackage = lib.model propel.packageObjectModel = true propel.project = blog propel.database = mysql propel.database.createUrl = mysql://localhost/ propel.database.url = mysql://blog:blog@localhost/blog propel.mysql.tableType = InnoDB
Listing 2
Die Datei databases.yml verwendet die Markup-Sprache YAML (Yet Another Markup Language), die Symfony-weit Verwendung für Konfigurationsdateien findet. Das Markup ist sehr empfindlich, denn Einrückungen mit Leerzeichen sorgen für die Semantik. Tipp: Benutzen Sie keine Proportionalschriftart und keine Tabulatoren im Editor!
all: propel: class: sfPropelDatabase param: phptype: mysql phptype: mysql host: localhost database: blo username: blog
Listing 3
ORM – Object-Relational-Mapping
Danach sorgt das Kommando „symfony propel-build-all“ dafür, dass aus dem DB-Schema ein für die konfigurierte Datenbank passendes SQL-File erzeugt und direkt in die Datenbank geschrieben wird. Gleichzeitig erzeugt der Codegenerator etliche PHP-Klassen, die uns unter Verwendung des ORM-Layers (Object-Relational-Mapping) „Propel“ komfortablen Zugriff auf unsere Datenbank ermöglichen. Anders gesprochen: Jede in unserem DB-Schema definierte Relation bekommt eine eigene PHP-Klasse. Einen neuen Blog-Eintrag können wir jetzt komfortabel ohne SQL anlegen:
$blog_entry = new BlogEntry(); $blog_entry->setTitle('Hallo!'); $blog_entry->setContent('Ich bin ein Inhalt!'); $blog_entry->save();
Listing 4
Keine Angst, das DB-Schema kann später problemlos erweitert werden. Der Codegenerator wird durch eine schlaue Vererbungsstruktur keine unserer manuellen Änderungen überschreiben, sondern nur die zugrundeliegenden Basisklassen manipulieren.
Im Dateisystem finden wir die erzeugten Klassen im Verzeichnis lib/model. Während die Basisklassen in den Verzeichnissen lib/model/om und lib/model/map nicht modifiziert werden dürfen, können die von den Basisklassen abgeleiteten Klassen direkt in lib/model den persönlichen Anforderungen angepasst werden. Sie werden vom Codegenerator nur angelegt, wenn sie noch nicht vorhanden sind, nie jedoch überschrieben. In Symfony teilen sich alle Anwendungen eines Projekts das gleiche Datenmodell.
Erste Schritte zur Webanwendung
Unser Blog wird eine Frontend-Anwendung mit nur einem Modul zur Anzeige und zum Bearbeiten von Blogeinträgen. Da die Sandbox-Installation bereits eine Frontend-Applikation mitbringt, müssen wir lediglich mit „symfony propel-generate-crud frontend blog BlogEntry“ ein Modul anlegen, das schon grundlegende Create-, Retrieve-, Update- und Delete-Mechanismen für unseren im Schema definierten Blog-Datentyp bietet. Mit anderen Worten: Symfony kann das komplette Gerüst einer Webanwendung erzeugen, die unseren Blog-Datentypen verwaltet.
Wenn man diese vorgefertigten CRUD-Operationen nicht braucht, kann man sich auch ein nacktes Modul anlegen und die Funktionalität manuell programmieren. Wir verlassen uns hier jedoch auf die Symfony-Komfortmerkmale und lassen uns die CRUDs mit obigem Kommando erzeugen. Nach dieser Operation hat uns der Codegenerator eine komplette Verzeichnisstruktur samt PHP-Klassen in MVC-Struktur angelegt, wir können unsere Anwendung sogar schon im Browser aufrufen:
http://localhost/sf_sandbox/web/frontend_dev.php/blog
Listing 5
Bisher haben wir noch keine Zeile programmiert.
Environments für Entwicklung und Produktion
Um einfach zwischen Entwicklungsumgebung und Produktionsumgebung in der Webanwendung unterscheiden zu können, bietet Symfony unterschiedliche Environments an. Eine Entwicklungsumgebung der Webanwendung erreicht man über den Dispatcher „frontend_dev.php“, die Produktionsumgebung direkt über „index.php“. In der Entwicklungsumgebung stehen zahlreiche Diagnose- und Debugging-Funktionen in einer Toolbar auf der Weboberfläche zur Verfügung. Im Produktionsbetrieb werden keine Debugging-Informationen zur Verfügung gestellt, die Anwendung wird durch den Einsatz von Caching-Mechanismen deutlich performanter.
Wenn man sich die Dispatcher genauer ansieht, stellt man fest, dass das Environment lediglich durch eine Konstantendefinition festgelegt wird:
define('SF_ENVIRONMENT', 'dev'); define('SF_DEBUG', true);
Listing 6
Doch zurück zu unserem Blog-Modul.
Model View Controller
Unser Modulverzeichnis „blog“ hat einige Unterverzeichnisse bekommen, welche die Grundstruktur einer MVC-Anwendung widerspiegeln. Im „actions“-Verzeichnis liegt der Controller-Teil der Anwendung, während im „templates“-Verzeichnis die dazugehörigen Views untergebracht sind. Jeder Action muss ein Template zugeordnet werden, zum Beispiel findet die Controller-Funktion executeShow() im Template showSuccess.php ihre View. Durch diese Aufteilung werden Logik (Controller) und Präsentation (View) strikt voneinander getrennt. Während die jeweiligen Actions direkten Zugriff auf das Datenmodell haben, sind die Templates nur für die Anzeige zuständig.
Da unser Blog bisher noch sehr hässlich ist (wir haben uns bisher nur Standard-Ansichten von Symfony erzeugen lassen), müssen wir also an den Dateien im templates-Verzeichnis arbeiten. Zuerst werfen wir einen Blick auf die Datei showSuccess.php. Der Name verrät, dass diese Datei das Ergebnis der „show“-Action darstellen soll. Die „show“-Action selbst kann durch einen Rückgabewert bestimmen, welches Template zur Anzeige verwendet werden soll. In unserem Beispiel bleiben wir beim Standard und lassen alle Actions „Success“ ausgeben. Wer möchte, kann einer Action auf diese Weise beliebig viele Templates zuordnen: „showError.php“ etwa ist ein Template der Action „show“ und wird angezeigt, wenn die Action „Error“ zurückgibt. Die Verwendung von Camel-Case ist an dieser Stelle obligatorisch, sonst kann Symfony Action und Template nicht zueinander in Verbindung bringen.
Um das Konzept zu verstehen, werfen wir einen Blick in die Datei actions.class.php:
public function executeIndex() { return $this->forward('blog', 'list'); } public function executeList() { $this->blog_entrys = BlogEntryPeer::doSelect(new Criteria()); }
Listing 7
In diesem Code-Ausschnitt werden zwei Actions definiert: Index und List. Index ist immer die Default-Action eines Moduls und verweist in diesem Beispiel einfach weiter auf die List-Action. Die „forward“-Funktion erwartet als Parameter den Namen des Moduls und den Namen der Action innerhalb dieses Moduls, die angesprungen werden soll. In der List-Action wiederum wird die Member-Variable „blog_entrys“ unter Verwendung des ORM-Layers mit einer Liste von Blog-Einträgen befüllt. Symfony fügt bei Variablennamen, die eine Liste von mehreren Objekten beinhalten, automatisch ein Plural-s am Ende an (daher steht hier im automatisch erzeugten Code nicht „blog_entries“, sondern „blog_entrys“). Das statische Objekt BlogEntryPeer wird von Propel bereitgestellt, um die eigentlichen BlogEntry-Objekte zu verwalten. Dementsprechend wird hier auf dem Peer-Objekt die Funktion „doSelect“ aufgerufen, um eine Liste von blog_entry-Objekten zu erhalten. Bisher befüllt die Action also lediglich eine Member-Variable mit einer Liste. Alles weitere findet im Template listSuccess.php statt:
<?php foreach ($blog_entrys as $blog_entry): ?> <tr> <td><?php echo link_to($blog_entry->getId(), 'blog/show?id='.$blog_entry->getId()) ?></td> <td><?php echo $blog_entry->getTitle() ?></td> <td><?php echo $blog_entry->getContent() ?></td> <td><?php echo $blog_entry->getDeleted() ?></td> <td><?php echo $blog_entry->getCreatedAt() ?></td> <td><?php echo $blog_entry->getUpdatedAt() ?></td> </tr> <?php endforeach; ?>
Listing 8
Hier steht die oben gefüllte Member-Variable im globalen Scope zur Verfügung und kann einfach durchiteriert werden. Die „link_to“-Helper-Funktion erzeugt in der Ausgabe einen Link auf den jeweiligen Blog-Eintrag. In den über den Softlink verfügbaren Quellen zu diesem Tutorial haben wir die Tabelle aus dem obigen Template verbannt und die Gesamterscheinung mit CSS ansehnlicher gestaltet.
Sessionverwaltung und Authentifizierung
In unser Blog soll nicht jeder schreiben dürfen, sondern nur ein authentifizierter Benutzer, der über ein Passwort identifiziert wird. Dazu machen wir uns die Symfony-eigene Sessionverwaltung zu Nutze. Zuerst legen wir in der Konfigurationsdatei security.yml fest, welche Anwendungsteile geschützt werden sollen:
show: is_secure: on create: is_secure: on edit: is_secure: on update: is_secure: on delete: is_secure: on
Listing 9
Jede einzelne Action des Moduls kann mit Sicherheitsmerkmalen versehen werden, in unserem einfachen Beispiel mit „is_secure“. Eine dermaßen geschützte Action wird nur ausgeführt, wenn ein User-Objekt vorhanden ist und der Benutzer den Status „isAuthenticated“ aufweist. Auf die User-Credentials kann in einem Template einfach zugegriffen werden:
<?php if($sf_user->isAuthenticated()) echo link_to ('new entry', 'blog/create'); ?>
Listing 10
Die Benutzerauthentifizierung selbst passiert üblicherweise in einer Action, die Login und Passwort etwa mit einer Datenbank abgleicht.
$this->getUser()->setAuthenticated(true);
Listing 11
Fazit
Das Symfony-Projekt gehört trotz seines vergleichsweise jungen Alters zu den bestdokumentierten Open-Source-Projekten überhaupt. Die Website des Projekts bietet neben einem kompletten Handbuch auch komplexere Tutorials zum Download an. So oder so erfordert jedoch die Software-Entwicklung mit Symfony, dass man sich mit grundlegenden Prinzipien der objektorientierten Programmierung und der MVC-Architektur auseinandersetzt. Letztendlich sorgt Symfony durch seine klaren Strukturen dafür, dass der erzeugte Programmcode sauber und einfach wartbar ist.