Front-Controller

1. Einleitung

Der Front-Controller ist neben dem Page-Controller 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.

Der Front-Controller stellt gemäß dem nachfolgend gezeigten Timing-Modell eine Möglichkeit dar, in den Abarbeitungs-Prozess des Frameworks einzugreifen. Er stellt Ihnen die Basis bereit, um vor dem Aufbau der Page-Controller-Seite bereits für die Applikation wichtige Informationen zu sammeln und bereit zu stellen bzw. vor oder nach der Transformation weitere Prozesse abzubilden.

Front-Controller timing model

Vor allem in komplexen Web-Anwendungen ist es oft notwendig, die Bestandteile der Seite bereits vor dem Aufbau zu kennen um wesentliche Bestandteile je nach Anforderungen des Benutzers flexibel ändern zu können (z.B. Login). Weitere Ideen und Möglichkeiten zur Umsetzung können dem Kapitel Modelbasiertes View-Konzept entnommen werden. Um Actions zu definieren, die bei jedem Request ausgeführt werden, hat der Entwickler die Möglichkeit diese Actions in der Bootstrap-Datei zu registrieren. Diese verhalten sich gemäß dem Timing Model wie "normale" Actions.

Die Front-Controller-Komponente des Applikations-Frameworks besteht aus einer Singleton-Instanz der Klasse Frontcontroller. Mit Hilfe eines geeigneten Input-Filter werden die Request-URL und Parameter ausgewertet und die darin codierten Anweisungen ausgeführt (siehe Timing-Modell).

Für die Implementierung von eigenen Actions bietet das Adventure PHP Framework zwei Basisklassen: AbstractFrontcontrollerAction für Actions und FrontcontrollerInput für eigene Input-Klassen.

Die Wiki-Seite Unterschied Front-Controller und Page-Controller greift nochmals wesentliche Merkmale des Front-Controller auf und beschreibt das Anwendungsgebiet.

2. Implementierung

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

Die Trennung zwischen Implementierung und Konfiguration sorgt dafür, dass die Implementierung selbst nach aussen versteckt wird und zusätzliche Abhängigkeiten und Konfigurationen transparent aufgelöst werden können.

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 { public function run(){ echo 'I am front controller action class! My Name is '. $this->input->getAttribute('Name').'!'; } } class DemoInput extends FrontcontrollerInput { public function __construct(){ $this->setAttribute('Name', 'Max Mueller'); } }

Wird die hier gezeigte Action ausgeführt, erscheint im Browser der Satz

Code
I am front controller action class! My Name is Max Mueller!
Ab Version 1.14 sind Input-Klassen optional. Ist die Logik der Klasse auf das Aufnehmen und Wiedergeben von Action-Parametern beschränkt, so ist es nicht notwendig eine eigene Klasse zu implementieren, sondern der Front-Controller kann über die Konfiguration veranlasst werden, die mit dem APF mitgelieferte Klasse FrontcontrollerInput zu nutzen.

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.

Innerhalb einer Action kann bequem über die Variable $this->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 gemäß dem APF-Konfigurations-Schema unter dem Verzeichnis des Action-Namespaces abgelegt.

Wird eine Action mit dem Namespace modules::captcha::biz und dem Namen showCaptcha über die URL oder durch direkte Registrierung beim Front-Controller zur Ausführung angefordert, so erwartet er eine Konfigurations-Datei mit dem Namen actionconfig.ini unter dem genannten Namespace. Ist die aktuelle APF-Installation mit dem Context projectone und dem Standard-Einstellungen für die Umgebung eingestellt, so wird die Konfiguration unter

Code
/APF/config/modules/captcha/biz/projectone/DEFAULT_actionconfig.ini

erwartet. Eine Action-Konfiguration beinhalten dabei ein oder mehrere Definitionen von Actions, die wie folgt aussehen:

APF-Konfiguration
[{ActionName}] FC.ActionNamespace = "" FC.ActionClass = "" [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.ActionClass:
    Name der Action-Klasse (Beispiel: LoadModelAction).
  • 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 "|").
Wird keine eigene Input-Implementierung benötigt, so kann dies durch Weglassen des FC.InputClass-Parameters in der Konfiguration definiert werden. Eigene Implementierungen sind üblicherweise nur dann sinnvoll, wenn Action-Parameter weiterverarbeitet werden sollen.

2.3. Aufbau der Bootstrap-Datei

Um eine Applikation mit dem Front-Controller zu bereiben, muss die Bootstrap-Datei (index.php) folgendes enthalten:

PHP-Code
require('./apps/core/pagecontroller/pagecontroller.php'); import('core::frontcontroller','Frontcontroller'); $fC = &Singleton::getInstance('Frontcontroller'); $fC->setContext('sites::demosite'); $fC->setLanguage('de'); echo $fC->start('sites::demosite::pres::templates','website');
Seit Release 1.13 wird das Ergebnis der Verarbeitung nicht mehr direkt ausgegeben, sondern muss per echo an den Client geschickt werden. Dies ermöglicht es nun, die Ergebnisse auch in UnitTests o.ä. zu verarbeiten.

Da der Front-Controller den Page-Controller nutzt, muss dieser zuvor mit einem include() oder require() eingebunden werden.

Wie das Ablauf-Diagram in Kapitel 1 zeigt, werden während der Ausführung die Input- und Output-Filter ausgeführt, die dafür sorgen, dass der aktuelle Request hinsichtlich seiner URL-Struktur verarbeitet wird. Der Input-Filter sorgt dafür, dass die Action-Anweisungen aus der URL gelesen und an den Front-Controller übergeben werden. Soll ein eigenes URL-Layout implementiert werden, so muss dieser Umstand im dafür vorgesehenen Code Beachtung finden.

Ab Version 1.14 werden die Input- und Output-Filter nur noch in Verbindung mit dem Front-Controller ausgeführt. Damit kann der Page-Controller zwar eigenständig zur HTML-Erzeugung eingesetzt werden, stellt aber konzeptionell nicht mehr die führende Komponente der Request-Verarbeitung dar.
Ist es gewünscht, eine Action bei jedem Request automatisiert auszuführen, kann diese vor dem Starten des Front-Controller per
PHP-Code
$fC->registerAction('sites::demosite::biz','Login');
registriert werden. Dabei ist es unerheblich um welche Art von Action (prepagecreate, ...) es sich handelt.

Wie in der Definition des gleichnamigen Design-Pattern beschrieben ist, ermöglicht der APF Front-Controller die Ausführung einer beliebigen Anzahl von Actions innerhalb eines Requests. Diese unterliegen zusätzlich einem Timing-Modell das es ermöglicht, Actions zu ganz unterschiedlichen Zeitpunkten auszuführen.

Die Steuerung der Ausführung der Actions obliegt dabei dem Front-Controller, die Registrierung der auszuführenden Actions übernimmt jedoch der zum Front-Controller passende Filter oder der Entwickler in Form von registerAction()-Methoden-Aufrufen.

Bitte beachten Sie, dass die Information in Kapitel 2.4.3 bis einschließlich 2.4.6 mit dem Release 1.14 veraltet sind. Bitte nutzen Sie die im Kapitel Links beschriebene Möglichkeit Links mit Front-Controller-Actions zu erzeugen!
2.4.1. URL-Layout

Um die Möglichkeit von mehrfach pro Request ausführbaren Actions auch über die URL zur Verfügung zu stellen, wurde ein Layout definiert, in dem beliebig viele Actions mit einer beliebigen Anzahl von Parametern zur Ausführung definiert werden können. Die natürliche Grenze bildet dabei die Beschränkung der Zeichen-Anzahl in der URL. Das Schema für reguläre URLs ist wie folgt definiert:

Code
{namespace}-action:{config-name}={param1}:{value1}|{param2}:{value2}|...

Dabei entspricht {namespace} dem Namespace der Action-Konfigurations-Datei und {config-name} dem Namen der Konfigurations-Sektion, die die Action-Implementierung definiert. Die Parameter-Sätze sind jeweils durch "|" getrennt, Name und Wert durch ":". Wie zuvor beschrieben, ist es möglich dass, mehrere Action-Anweisungen und Standard-Request-Parameter in der URL zu platzieren. Das folgende Beispiel zeigt zwei Actions und weitere Steuer-Parameter einer Anwendung.

Code
?projects_projectone-action:setModel=pageid:1|lang:de&news-page=3&projects_projectone-action:stat=action:view|referer:32
Bitte beachten Sie, dass die Reihenfolge der Action-Definitionen die Reihenfolge der Ausführung für Actions gleichen Typs vorgibt!

Für Rewrite URLs wurde das Schema so gewählt, dass weiterhin eine Trennung zwischen "normalen" und Action-Parametern möglich ist. Als Trennzeichen fungiert "/~/". Analog zum Schema für normale URLs lässt sich eine Action im Rewrite-Fall wie folgt adressieren:

Code
/~/{namespace}-action/{config-name}/{param1}/{value1}/{param2}/{value2}/...

"Normale" Parameter werden dabei ebenso mit dem genannten Trennzeichen von Action-Anweisungen separiert wie Actions gegenseitig. Für das obige Beispiel gestaltet sich die umgeschriebene URL wie folgt:

Code
/~/projects_projectone-action/setModel/pageid/1/lang/de/~/news-page/3/~/projects_projectone-action/stat/action/view/referer/32
Der hier gewählte Ansatz ist auf Allgemeingültigkeit ausgelegt. Soll aus SEO- oder anderen Gründen ein anderes URL-Layout gewählt werden, ist das auf Basis der Input-Filter jederzeit möglich. Action-Aufrufe können sowohl mit Hilfe von RewriteRules als auch durch Anpassung der Filter generiert werden, die die Informationen in der URL decodieren. Hinweise zur Implementierung von eigenen Filtern finden sich im Wiki.
2.4.2. Action-Steuerung

Die abstrakte Klasse AbstractFrontcontrollerAction definiert noch einige weitere Mechanismen, die zur Steuerung der Action eingesetzt werden können. Neben dem Timing und der permanenten Ausführung einer Action kann diese dynamisch aktiviert und deaktiviert werden. Dies ermöglicht die Action nicht oder erst dann auszuführen, wenn eine andere Action auf dem Action-Stack geladen ist oder eine andere Bedingung zutrifft.

Hierzu kann die Methode isActive() der Klasse AbstractFrontcontrollerAction überschrieben und mit der gewünschten Logik versehen werden:

PHP-Code
public function isActive() { $captcha = &$this->getParentObject()->getActionByName('showCaptcha'); return $captcha === null; }

Der beschriebene Code aktiviert die Action nur dann, wenn die Action showCaptcha nicht angefragt ist.

Diese Funktionalität steht ab dem Release 1.13 zu Verfügung!

2.5. Timing-Model

Der Front-Controller 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 (TYPE_PRE_PAGE_CREATE): Action wird vor dem Erzeugen der Page-Controller-Seite ausgeführt.
  • postpagecreate (TYPE_POST_PAGE_CREATE): Action wird nach dem Erzeugen der Page-Controller-Seite ausgeführt.
  • pretransform (TYPE_PRE_TRANSFORM): Action wird vor der Transformation der Page-Controller-Seite ausgeführt.
  • posttransform (TYPE_POST_TRANSFORM): Action wird nach der Transformation der Page-Controller-Seite ausgeführt.

Das Timing kann nur zur Entwicklungszeit per

PHP-Code
class MyAction extends AbstractFrontcontrollerAction { // Action-Timing zum Zeitpunkt der Klassen-Deklaration festlegen (Release <= 1.13) protected $type = 'pretransform'; // Action-Timing zum Zeitpunkt der Klassen-Deklaration festlegen (Release > 1.13) protected $type = self::TYPE_PRE_PAGE_CREATE; public function __construct() { // Action-Timing zum Zeitpunkt der Instanziierung festlegen (Release <= 1.13) $this->type = 'pretransform' // Action-Timing zum Zeitpunkt der Instanziierung festlegen (Release > 1.13) $this->type = self::TYPE_PRE_PAGE_CREATE } public 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 Front-Controller möglich ist, die Business-Schicht, bzw. das Model der Anwendung vor der Präsentationsschicht zu erzeugen, kann, im Gegensatz zu einer Page-Controller-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 Front-Controller-basierten Anwendung auch im Bereich des GUI-Designs voll ausschöpfen zu können wurde den HTML-Tools die TagLib FrontControllerImportTemplateTag 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 Front-Controller-Actions bedient und gefüllt wird. Hierzu wird in der Regel eine eigene Klasse der Form

PHP-Code
class SiteModel extends APFObject { public function __construct() { $this->setAttribute('view.content.template', 'login'); $this->setAttribute('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="SiteModel" modelclass="SiteModel" 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" class="FrontControllerImportTemplateTag" prefix="fcon" name="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 können dem Kapitel Services 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 Page-Controllers 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
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.
2
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
3
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.
4
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.