Frontcontroller

1. Einleitung

Der Front-Controller ist neben dem Pagecontroller ein weiterer wichtiger Basis-Bestandteil des Frameworks. Basis für die hier zur Verfügung stehende Implementierung ist die Pattern-Definition von Martin Fowler und die Auslegung des Patterns im JAVA-Framework Struts. Einführende Beispiele können auf der PHP-Patterns- Webseite eingesehen werden.

Die FrontController-Komponente des Applikations-Frameworks besteht aus einer Singleton-Instanz der Klasse FrontController, die den Benutzer-Request auswertet und die enthaltenen Anweisungen ausführt. Es gibt mehrere Arten von Actions, die gemäß dem in der folgenden Grafik beschriebenen Timing Model ausgeführt werden:

Frontcontroller timing model

Im Allgemeinen werden Actions, die das Schlüsselwort "pre" enthalten, vor einer definierten Aktion ausgeführt und Actions mit "post" in der Typenbezeichnung nach einem Code-Teil. Aktionen des Typs "prepagecreate" dienen meist zum Aufbau des Models einer Applikation, Actions des Typs "posttransform" z.B. für Logging-Aufgaben oder Ähnliches. Um eigene Actions zu implementieren bietet das Framework zwei abstrakte Basisklassen für Actions (AbstractFrontcontrollerAction) und ihre Informationen (FrontcontrollerInput). Der FrontControllerInputFilter ist eine Komponente, die die Action-Anweisungen aus der URL extrahiert, so dass der FrontController diese ausführen kann. Mit dieser Komponente kommt der Entwickler aber in der Regel nicht in Berührung, da diese vom FrontController selbstständig ausgeführt werden. Um Actions zu definieren, die bei jedem Request ausgeführt werden, hat der Entwickler die Möglichkeit diese Actions in der index.php zu registrieren. Diese verhalten sich gemäß dem Timing Model wie normale Actions. Das folgende UML-Diagramm zeigt die Komponenten des FrontControllers in der Übersicht:

Frontcontroller Actions Inputs UML

2. Implementierung

Die Implementierung im FrontController-Style beinhaltet im Wesentlichen zwei Bereiche. Zum einen muss eine Action- und Input-Klasse erzeugt werden, die jeweils von AbstractFrontcontrollerAction und FrontcontrollerInput ableiten, zum anderen muss eine Konfiguration angelegt werden, die eine Action beschreibt.


2.1. Action- und Input-Klassen

Die vom Entwickler erzeugte Action-Klasse kapselt die Funktion einer Action. Wie der API-Dokumentation der Klasse AbstractFrontcontrollerAction zu entnehmen ist, muss hierzu die Methode run() implementiert werden, da diese bei der Ausführung einer Action durch den FrontController angesprochen wird. Die Input-Klasse ist eine einfache Daten-Klasse die die Model-Informationen der jeweiligen Action beinhaltet. Ein Input-Objekt kann im einfachsten Anwendungsfall auch das Model einer Applikation sein. Das folgende Code-Beispiel zeigt zwei einfache Action- und Input-Klassen:

PHP-Code
class DemoAction extends AbstractFrontcontrollerAction { function DemoAction(){ } function run(){ echo 'I am front controller action class! My Name is '. $this->__Input->getAttribute('Name').'!'; } } class DemoInput extends FrontcontrollerInput { function DemoInput(){ $this->__Attributes['Name'] = 'Max Mueller'; } }
Wird die hier gezeigte Action ausgeführt, erscheint im Browser der Satz
APF-Template
I am front controller action class! My Name is Max Mueller!
Da die Actions ebenso den Context der Applikation kennen, können in den Actions beliebige Applikations-Teile verpackt werden. Ein beliebtes Beispiel ist die Benutzer-Authentifizierung. In der Action kann dann abgeprüft werden, ob der Benutzer eingeloggt ist oder nicht und ggf. die Model- Informationen des Login-Moduls setzen.

Hinweis: Innerhalb einer Action kann bequem über die Variable $__Input auf das Input-Objekt zugegriffen werden. Besteht die Notwendigkeit innerhalb einer Applikation von Außen auf die Action-Input-Informationen zugreifen zu müssen, so kann auf die Instanz einer Action die Methode getInput() angewendet werden. Diese liefert das korrespondierende Input-Objekt als Referenz zurück.


2.2. Konfiguration

Jede Action muss in einer Konfigurations-Datei definiert sein. Zur Definition gehören der Namespace, die Dateinamen für Action- und Input-Klassen, deren Namen selbst und evtl. benötigte Model-Informationen. Konfigurations-Dateien werden immer unter dem Verzeichnis
APF-Template
{ActionNamespace}::actions::{CONTEXT}
und mit dem Namen
APF-Template
{ENVIRONMENT}_actionconfig.ini
abgelegt. {ENVIRONMENT} ist dabei gegen den Wert der Umgebungsvariable zu ersetzen. Im Standard-Fall ist dies der Wert DEFAULT. Eine Action-Konfiguration beinhalten dabei ein oder mehrere Definitionen von Actions, die wie folgt aussehen:
APF-Template
[{ActionName}] FC.ActionNamespace = "" FC.ActionFile = "" FC.ActionClass = "" FC.InputFile = "" FC.InputClass = "" FC.InputParams = ""
Die aufgeführten Parametern haben dabei folgende Bedeutungen:
  • ActionName:
    Name der Action. Dieser Name wird in der URL als Action-Name verlangt (Beispiel: setModel).
  • FC.ActionNamespace:
    Namespace der Konfigurationsdatei der Action (Beipsiel: sites::demosite::biz::actions).
  • FC.ActionFile:
    Name der Datei, in der die Action-Klasse implementiert ist (Beispiel: LoadModelAction).
  • FC.ActionClass:
    Name der Action-Klasse (Beispiel: LoadModelAction).
  • FC.InputFile:
    Name der Datei, in dem die Input-Klasse residiert (Beispiel: DemositeModel).
  • FC.InputClass:
    Name der Input-Klasse (Beispiel: DemositeModel).
  • FC.InputParams:
    Konfigurations-Direktive, mit der Model-Daten per Konfiguration gesetzt werden können (Beispiel: login:true|headview:menu. Schlüssel und Wert sind per ":" getrennt, unterschiedliche Wertepaare per "|").
Da FrontController-Actions in der Regel Teile der Business-Schicht sind, sollten diese unter dem biz-Ordner einer Seite, Applikation oder eines Moduls abgelegt werden. Sinnvollerweise legt der Entwickler im Ordner biz noch einen Unterordner actions an, um die Übersichtlichkeit zu verbessern. Da Namespace, Namen der Dateien und Klassen frei wählbar sind, ist dem Entwickler hinsichtlich der Benennung und Strukturierung freie Hand gelassen.


2.3. Änderungen in der index.php

Um eine Applikation im FrontController-Style laufen zu lassen, sollte in der index.php folgendes eingetragen sein:
PHP-Code
// Instanz des Frontcontroller holen/erzeugen $fC = &Singleton::getInstance('Frontcontroller'); // Context der Applikation setzen $fC->set('Context','sites::demosite'); // Sprache der Applikation setzen $fC->set('Language','de'); // Seite generieren $fC->start('sites::demosite::pres::templates','website');
Ist es erwünscht, "permanente" Actions auszuführen, so ist ein
PHP-Code
// Standard-Action "Login" mitteilen $fC->registerAction('sites::demosite::biz','Login');
vor dem Aufruf der Methode start() notwendig. Anschließend ist es jederzeit möglich Actions von Modulen oder Applikationen ausführen zu lassen.


Um Links in FrontController-basierten Applikationen ebenso einfach generieren zu können wie bei PageController-Anwendungen wurde die Komponente FrontcontrollerLinkHandler eingeführt. Diese manipuliert einen Link auf Basis einer bestehenden URL und bettet alle in der URL erwünschten Action-Definitionen aus dem aktuellen Input-Objekt der Action ein.

Diese Verhaltensweise wird nur erreicht, sofern in der gewünschten Action die Klassen-Variable $__KeepInURL im Konstruktor auf true gesetzt wurde. In der Standard-Definition enthält diese den Wert false. Andernfalls werden alle Action-Anweisungen aus der generierten URL entfernt. Damit ist es möglich, komplexe Applikationen zu entwerfen, in denen unabhängige Module über eine lose URL-Kopplung koexistieren.

Das folgende Beispiel zeigt, wie der FrontcontrollerLinkHandler angewendet wird. Der Einfachheit halber werden Links nur im Rewrite-Modus betrachtet. Die Manipulation ohne URL-Rewriting funktioniert analog dazu.

Das Link-Schema einer Front-Controller-Action-Anweisung in der URL lautet
APF-Template
{namespace}-action:{name}={key1}:{value1}|{key2}:{value2}|...
bzw.
APF-Template
/{namespace}-action/{name}/{key1}/{value1}/{key2}/{value2}/...
im Fall von URL-Rewriting. Bei normalen URLs dient & oder ? als Trenn-Element zwischen Parametern und Action-Anweisungen, im Fall von URL-Rewriting müssen normale Parameter mit /~/ von Action-Anweisungen separiert werden.


2.4.1. Einfache Manipulation von Parametern
In vielen Applikationen ist es notwenig Links zu generieren. In Frontcontroller-basierten Anwendungen, in denen keine Action-Anweisungen in URLs manipuliert werden müssen, kann der FrontcontrollerLinkHandler wie sein "kleiner Bruder" LinkHandler eingesetzt werden um URLs zu erzeugen. Aus einem Link
APF-Template
http://adventure-php-framework.org/Seite/ChangeLog/benchmarkreport/true/param1/value1/param2/value2
kann auf einfache Weise
APF-Template
http://adventure-php-framework.org/Seite/Guestbook/benchmarkreport/true
generiert werden. Hierzu ist lediglich ein
PHP-Code
// URL definieren $URL = 'http://adventure-php-framework.org/Seite/ChangeLog/benchmarkreport/true/param1/value1/param2/value2'; // URL-Aenderungen definieren $ChangeParams = array( 'Seite' => 'Guestbook', 'param1' => '', 'param2' => '' ); echo FrontcontrollerLinkHandler::generateLink($URL,$ChangeParams);
notwenig.


2.4.2. Manipulation von Parametern und Actions
Actions werden gleichermaßen als URL-Parameter behandelt wie "normale" URL-Bestandteile. Daher gestaltet es sich sehr einfach, aus einer URL
APF-Template
http://adventure-php-framework.org/Seite/ChangeLog/benchmarkreport/true/param1/value1/param2/value2
den Link
APF-Template
http://adventure-php-framework.org/Seite/Guestbook/benchmarkreport/true/param1/value1/param2/value2/~/ modules_guestbook_biz-action/LoadEntryList/pagesize/20/pager/false/adminview/true
zu generieren. Hier wurde lediglich durch ein geändertes Parameter-Array eine Action hinzugefügt:
PHP-Code
// URL definieren $URL = 'http://adventure-php-framework.org/Seite/ChangeLog/benchmarkreport/true/param1/value1/param2/value2'; // URL-Aenderungen definieren $ChangeParams = array( 'modules_guestbook_biz-action:LoadEntryList' => 'pagesize:20|pager:false|adminview:true', 'Seite' => 'Guestbook' ); // Link generieren echo FrontcontrollerLinkHandler::generateLink($URL,$ChangeParams);
Wurde die in der URL darzustellende Action bereits als "permanente" Action in der index.php eingehängt ist es lediglich notwendig, den FrontcontrollerLinkHandler aufzurufen und die gewünschten "normalen" Parameter zu manipulieren. Dabei werden die vorhandenen Actions automatisch in die URL eingefügt.


2.4.3. Manipulation von Parametern und Actions mit generateURLParams()
Das Beispiel in Kapitel 2.4.2. hat den Nachteil, dass sich der Entwickler Gedanken über die Semantik einer FrontController-URL machen muss. Um URL-Generierung noch komfortabler zu gestalten, wurde dem FrontcontrollerLinkHandler die Methode generateURLParams hinzugefügt. Diese ermöglicht, aus einem Satz von Parametern eine Action-Anweisung in Form eines Arrays zu generieren, das der Methode generateLink übergeben werden kann. Die URL-Generierung kann dann wie folgt erfolgen:
PHP-Code
// URL definieren $URL = 'http://adventure-php-framework.org/Seite/ChangeLog/benchmarkreport/true/param1/value1/param2/value2'; // URL-Aenderungen definieren $ChangeParams = array( 'Seite' => 'Guestbook' ); // Parameter der FrontController-Action erzeugen $ChangeParams = array_merge( $ChangeParams, FrontcontrollerLinkHandler::generateURLParams( 'modules::guestbook::biz', 'LoadEntryList', array( 'pagesize' => '20', 'pager' => 'false', 'adminview' => 'true' ) ) ); // Link generieren echo FrontcontrollerLinkHandler::generateLink($URL,$ChangeParams);
Aus Performancegründen sollte diese Methode allerdings nicht übermäßig Verwendung finden, da diese bereits ~0.004 s zur Generierung des Parameter-Arrays benötigt. Ist klar, in welchem Betriebsmodus die Anwendung später laufen wird (Rewrite-URLs vs. normale URLs), sollte auf ein manuelles Generieren der URLs, wie in Kapitel 2.4.2. aufgezeigt, zurückgegriffen werden.


2.5. Timing-Model

Der FrontController besitzt ein eigenes vierstufiges Timing-Model, mit dem vom Entwickler beeinflusst werden kann, wann eine Action ausgeführt wird. Hierzu existiert in der Interface-Definition der Klasse AbstractFrontcontrollerAction das Attribut $__Type. Dieses ist standardmäßig mit dem Wert prepagecreate gefüllt. Soll die Action zu anderen Zeiten zum Einsatz kommen, so stehen folgende Werte für diesen Parameter zur Verfügung:
  • prepagecreate: Action wird vor dem Erzeugen der PageController-Seite ausgeführt.
  • postpagecreate: Action wird nach dem Erzeugen der PageController-Seite ausgeführt.
  • pretransform: Action wird vor der Transformation der PageController-Seite ausgeführt.
  • posttransform: Action wird nach der Transformation der PageController-Seite ausgeführt.
Das Timing kann nur zur Entwicklungszeit per
PHP-Code
class MyAction extends AbstractFrontcontrollerAction { // Timing festlegen var $__Type = 'pretransform'; function MyAction(){ } function run(){ } }
manipuliert werden. Implementierungsdetails zur Methode run() finden sich in der API-Dokumentation der Klasse Frontcontroller.


2.6. Model-basiertes View-Konzept

Da es mit dem Frontcontroller möglich ist, die Business-Schicht, bzw. das Model der Anwendung vor der Präsentationsschicht zu erzeugen, kann, im Gegensatz zu einer PageController-Implementierung, die Business-Schicht dazu verwendet werden, die Präsentationsschicht zu steuern. Der hier wichtigste Bereich ist die Steuerung von Views, bzw. der Inhalte der Views.

Um den Vorteil einer Frontcontroller-basierten Anwendung auch im Bereich des GUI-Designs voll ausschöpfen zu können wurde den HTML-Tools die TagLib fcon_taglib_importdesign hinzugefügt. Diese Komponente bindet, wie oben angedeutet, Views ein die im Model der Anwendung definiert werden. Üblicherweise wird für das Model ein eigenes Applikationsmodel definiert, das aus Frontcontroller-Actions bedient und gefüllt wird. Hierzu wird in der Regel eine eigene Klasse der Form
PHP-Code
class DemoSiteModel extends coreObject { function DemoSiteModel(){ $this->__Attributes['view.content.template'] = 'login'; $this->__Attributes['view.topmenu.template'] = 'empty'; } }
implementiert, die an den relevanten Stellen mit einem
PHP-Code
$Model =&$this->__getServiceObject('sites::demosite::biz','DemoSiteModel');
verwendet oder befüllt werden kann.

In einer Template-Datei kann dann mit einem
APF-Template
<fcon:importdesign templatenamespace="sites::apfdocupage::pres::templates" modelnamespace="sites::demosite::biz" modelfile="DemoSiteModel" modelclass="DemoSiteModel" modelparam="view.content.template" [sessionsingleton="true|false"] />
ein View in Abhängigkeit der Inhalte des Models eingebunden werden. Um die TagLib verwenden zu können muss diese jedoch dem aktuellen Document zunächst per
APF-Template
<core:addtaglib namespace="tools::html::taglib" prefix="fcon" class="importdesign" />
bekannt gemacht werden. Das Attribut sessionsingleton definiert, ob die verwendete Model-Klasse im Modus SESSIONSINGLETON oder SINGLETON erzeugt werden soll. Details zu dem Erzeugungsmodalitäten kann der API-Dokumentation der Klasse ServiceManager entnommen werden.

Mit diesem Konstrukt lässt sich die Einbindung von Views bequem über das Model einer Applikation steuern und die Verantwortung wird so komplett an die Business-Schicht übergeben. Des Weiteren erfährt die Implementierung an Flexibilität gegenüber der Verwendung des PageControllers als alleinige Steuerungskomponente für das Aussehen einer GUI.


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.
«   1   »
Einträge/Seite: | 5 | 10 | 15 | 20 |

1
Tostan 23.06.2009, 16:22:26
zum Abschnitt 2.6. Model-basiertes View-Konzept
erzeugt man das Applikationsmodell SessionSingleton, wie im Tutorial Frontcontroller beschrieben, dann muss man in der Template-Datei bei fcon:importdesign den Parameter sessionsingleton = "TRUE" angeben.
2
Christian 14.07.2009, 22:59:13
Hinweise zum Timing-Modell und zum Zugiff auf REQUEST-Parameter in Input-Klassen finden sich unter http://forum.adventure-php-framework.org/de/viewtopic.php?f=6&t=144.
3
Sebastian 18.09.2009, 09:45:53
Zum Unterpunkt Model-basiertes View-Konzept wäre imho eine Erweiterung sinnvoll, die auf die Navigation innerhalb der Anwendung eingeht.
Im Code Beispiel DemoSiteModel wird dies angedeutet, jedoch nicht näher darauf eingegangen
4
Christian 24.09.2009, 22:33:27
Hallo Sebastian,

ich plane hierzu in naher Zukunft noch einen erweiterten Artikel zum Thema Model based view design. Grundsätzlich kannst du jedoch über eine Front-Controller-Action jede Form der Navigation abbilden, sofern du damit die darunterliegenden Views beeinflusst (z.B. Content-Bereich). Um schöne URLs zu erreichen kann es dann sinnvoll sein, in einer Action die URL /en/My-pyge-title.html in den jeweiligen View im Model der Anwendung zu übersetzen. Soll es möglichst einfach gestaltet sein, reicht auch das Ansprechen der Action mit der Front-Controller-URL-Semantik.