FOL9000

Die f9-View-Klassen

von | 3 Kommentare

In der bisherigen Beschreibung des f9-Frameworks ging es um die Behandlung von Web-Requests (vgl. RequestDispatcher). Ein weiterer wichtiger Teil von f9 ist die Unterstützung der View-Schicht, d.h. der Ebene, die darüber entscheidet, wie die Daten dargestellt werden, die von der Anwendung als Reaktion auf einen Web-Request ermittelt und berechnet wurden. Dieser Artikel beschreibt die View-Klassen von f9.

Wie der Name schon sagt: Der View regelt, wie die Daten angezeigt werden sollen, neudeutsch: wie sie gerendert werden sollen. Dieselben Daten werden aber häufig in verschiedenen Formaten benötigt; sucht ein Mitarbeiter in der Kundenliste nach einem Kunden in Bonn, könnte das Ergebnis als HTML-Seite angezeigt werden, wird die gleiche Kundenliste als Input für ein Mailing-Programm benötigt, könnten JSON oder vielleicht CSV besser sein. Würde man dies alles in den Controller integrieren, hätte man dort nicht nur Code, der dort eigentlich nicht hingehört (denn es wird nichts aus verschiedenen Quellen zusammengestellt, sondern etwas erzeugt). Man würde den Controller-Code auch enorm überfrachten.

Besser ist es da, für jedes Format einen einzelnen, unabhängigen View vorzusehen und zusätzlich einen Resolver entscheiden zu lassen, welcher View in einem konkreten Falle den nun gefragt ist — aufgrund welcher Kriterien auch immer.

f9 stellt beides zur Verfügung: eine View-Klasse, die für ein spezielles Format (ggf. mit einem Ausgabe-Template) die Daten rendert und zwei ViewResolver, die nach leicht unterschiedlichen Kriterien einen passenden View aussuchen.

Der Ablauf ist dann folgender: Der Controller hat eine Methode eines Service aufgerufen und von dort die anzuzeigenden Daten zurück bekommen. Nun ruft er (oder in f9 der RequestDispatcher, der als Front-Controller ja auch ein Controller ist) den ViewResolver auf und bekommt ein View-Objekt zurück. Schließlich wird die Methode render() des View-Objekts vom Controller aufgerufen und der Output an den Client zurückgesandt.

Die View-Klasse

Kern des View-Handlings ist die View-Klasse. Grob gesagt hat sie zwei Aufgaben: Zunächst nimmt sie Modell-Daten auf, die es darzustellen gilt. Dann benötigt sie eine Template-Datei, in die die Modell-Daten eingefügt werden — in der Regel dürfte dies bei einem Web-Framework eine php- bzw. HTML-Datei sein. Und schließlich muss beides zusammengeführt werden; diesen Job übernimmt das Rendering.

Namespace

Als Teil des f9-Frameworks liegt die View-Klasse im net\f9-Namespace. Will man sie benutzen, ist die entsprechende Klasse also zunächst per use einzubinden:

use net\f9\View;

Setzen der Modell-Daten

Modell-Daten werden von der View-Klasse als Name/Wert-Paare erwartet. Dies ist ein wichtiges Detail, denn die Daten werden — wenn das Template ein php-Template ist — im Template als Variablen unter ihrem Modell-Namen zur Verfügung stehen. Deshalb ist es wichtig, dass die Namen der Modell-Daten gültige php-Variablen-Namen sind! Satz- und Sonderzeichen und v.a. Leerzeichen sind also tabu!

Das Setzen der Modell-Daten kann auf verschiedenen Wegen geschehen, die auch miteinander kombiniert werden können. Zunächst ist es möglich, dem Konstruktor ein assoziatives Array mit den Model-Daten zu übergeben. Will man dies nicht oder ist dies aus irgendeinem Grund nicht möglich, kann ein entsprechendes Modell-Array auch nachträglich gesetzt werden. Ganz wichtig ist es zu beachten, dass die setModel()-Variante alle eventuell vorher schon gesetzten Modell-Daten überschreibt! Will man dies nicht, aber trotzdem ein ganzes Array an Modell-Daten setzen, nutzt man die Methode mergeModel():

$model = new array('name'=>'fritz', 'email'=>'none');

$v = new View($model);

// Achtung: Modell-Daten aus Konstruktor werden durch setModel() 
// überschrieben, obwohl die Daten ungleiche Namen haben!
$model = new array('price'=>200, 'quantity'=>50);
$v->setModel($model);

// Auf diesem Wege werden neue Daten hinzugefügt:
$data = new array('stock'=>300);
$v->mergeModel($data);

// Modell-Daten sind jetzt:
// 'price'=>200, 'quantity'=>50, 'stock'=>300

Für den Fall, das auch dass zu umständlich ist oder nicht ganz passt, ist die View-Klasse schließlich auch ein ArrayObject. Das bedeutet, dass über set($name, $value) gesetzte Werte ebenfalls den Model-Daten hinzugefügt werden:

$view->set('quantity', 6745738);

Und zu wirklich allerletzt können die Modell-Daten auch ganz zum Schluss, beim Aufruf des Rendering gesetzt werden (zum Rendering aber unten mehr). In diesem Fall werden die neuen Daten den alten per mergeModel() hinzugefügt, bereits gesetzte Daten werden also nicht gelöscht.

$data = new array('stock'=>658);
$view->render($data);

Setzen der Template-Datei

Die dem View übergebenen Modell-Daten sollen in irgendeine Datei eingefügt werden; in der Regel ist dies eine php- bzw. HTML-Datei. Diese Template-Datei muss als weiteres übergeben werden. Dies kann relativ zu einem Basis-Verzeichnis geschehen (zu setzen mit setBasedir()) oder einfach absolut:

$view->setFile('/views/my_file.php');

Dazu, wie das Template aussehen muss und wie die Daten dort eingefügt werden können, mehr im Absatz zum Rendering.

Das eigentliche Rendering

Jetzt ist alles zusammen: Modell-Daten zum Anzeigen und ein Template, in das sie eingefügt werden sollen. Nun muss letzteres nur noch geschehen und die entstandene Datei zurück zum Client geschickt werden.

Sehr oft werden für Templates bzw. dafür, Daten in die Templates einzufügen, eigene Template-Sprachen eingesetzt. Im Java-Sektor ist dies im klassischen JSP bekannt (vgl. Oracles JSP-Seiten), dort aber auch nicht mehr ganz aktuell. In f9 geschieht das nicht. Zum einen nicht, weil eine eigene Template-Sprache ein Micro-Framework viel zu sehr aufblähen würde, zum anderen aber auch, weil php bereits eine Template-Sprache ist! Daniel Sentker hat dazu einen guten Artikel geschrieben. Ganz knapp kann man die Vorteile von php als Template-Sprache so zusammenfassen, dass man ohne weiteren Framework-Code alles nutzen kann, was php von sich aus bietet, incl. der IDE und IDE-Features wie Debugging.

In f9 sind die Template-Dateien deshalb ganz normale php-Dateien, in denen man machen kann, was man in jeder anderen php-Datei auch machen kann. Das mag vielleicht auch viel Raum für Dinge lassen, die man dort nicht machen sollte, aber das könnte man wahrscheinlich nur um den Preis eines sehr viel umfangreicheren Frameworks verhindern.

Um dies also alles ganz einfach zu machen, stellt f9 in den Template-Dateien alle Modell-Variablen unter ihrem Modell-Namen als php-Variablen zu Verfügung zur Verfügung.

im folgenden Beispiel wird ein einfaches Modell mit einem Preis und einem Artikel-Namen im View gesetzt. Natürlich fehlt hier noch das Setzen der Template-Datei etc., auch das direkte Instantiieren eines Views ohne ViewResolver wird man so nicht machen — das Beispiel ist eher Pseudo-Code und nur zur Demonstration des Prinzips gedacht. Ein ausführliches Beispiel folgt unten.

$template = "file.php";
$model = new array('price'=> 24,95, 'article'=>'CD');
$v = new View($model, $template);
$v->render();

Damit wären in der php-Template-Datei die beiden Variablen $price und $article direkt verfügbar:

<html>
<body>
  Der Preis des Artikels <?php echo $article; ?> 
  lautet <?php echo $price; ?>.
</body>
</html>

(Damit dies funktioniert, darauf habe ich oben schon hingewiesen, müssen die Namen bzw. Schlüssel der Modell-Daten gültigen php-Variablennamen entsprechen.)

Einfacher in der Anwendung dürfte es kaum gehen — genau richtig also für ein Micro-Framework.

Wo der View herkommt — der ViewResolver

Im obigen Pseudo-Beispiel war die View-Klasse direkt instantiiert worden, dies aber mit dem Hinweis, dass dieser Weg in der Praxis meist nicht beschritten werden dürfte. Der Grund dafür liegt darin, dass zur eigentlichen Auswahl des Views und zum Instantiieren eines View-Objekts i.d.R. der ViewResolver benutzt werden dürfte.

Die Aufgabe des ViewResolvers ist einfach: Ihm wird ein Name oder Schlüssel für einen View übergeben und er liefert das passende View-Objekt zurück. Im Prinzip stellt er also eine Art Factory dar. Unterscheiden können sich verschiedene View-Resolver dadurch, dass sie bei der Auswertung des Namens bzw. Schlüssels verschieden vorgehen.

In f9 gibt es zunächst zwei View-Resolver: Den SimpleViewResolver, und den BeanAwareViewResolver. Der SimpleViewResolver interpretiert den übergebenen Namen als Namen einer View-Klasse und versucht, eine Objekt dieser Klasse zu instantiieren und mit den Modell-Daten zu initialisieren. Der BeanAwareViewResolver dagegen versucht dies erst im zweiten Schritt; zunächst interpretiert er den übergebenen Namen als Namen einer Bean im Bean-Container und versucht, sie dort zu finden. Erst wenn dort keine passende View-Bean gefunden werden konnte, geht der BeanAwareViewResolver wie der SimpleViewResolver vor und versucht, den übergebenen Namen als Klassennamen eines Views zu interpretieren. Zu Beans und Container aber mehr, wenn dieser Teil von f9 beschrieben wird.

Es ist aber auch leicht möglich und auch so vorgesehen, eigene View-Resolver zu schreiben, denn ViewResolver ist nur ein Interface. So wäre beispielsweise ein View-Resolver denkbar, dem die Extension eines Requests übergeben wird und der daraus das Format und damit den zurückzugebenden View ableitet.

Hilfe zur Kommunikation – die ModelAndView-Klasse

Eine Klasse gilt es noch zu erwähnen. Zur leichteren Kommunikation zwischen Controllern und dem View-Resolver gibt es die Klasse ModelAndView. Wie der Name schon sagt, macht sie nicht anderes, als Model und View zusammenzufassen. Wobei wichtig ist, dass mit View hier nicht ein View-Objekt gemeint ist, sondern ein wie oben erläutert, ein Name bzw. Schlüssel mit dem der View-Resolver ein passendes View-Objekt erzeugen kann.

Wie das folgende Beispiel zeigt, kann ein Controller ein Objekt dieser Klasse zurückgeben und muss sich um nichts weiteres kümmern. Die Auflösung des View-Namens geschieht im View-Resolver hinter den Kulissen, der Anwender muss dies nicht aufrufen oder irgendwas selbst programmieren. Der Beispiel-Controller zeigt, wie Controller ein ModelAndView-Objekt zurückgeben können und den Rest f9 überlassen können. Als Anwender muss man also wirklich nur die eigentliche Aufgabe des Controllers programmieren, der Rest geschieht ‚automatisch‘.

class ExampleController {
  public function showUser() {
    $data = array('firstname' => 'Frank', 'lastname'  => 'Miller'));
    $mav = new ModelAndView('ExampleView', $data);
  }
}

Kein Beispiel!

Ein Beispiel gibt es diesmal nicht. Das hat einen einfachen Grund. So wie hier dargestellt, wird man die View-, vor allem aber die View-Resolver-Klassen im Rahmen von f9 praktisch nie einsetzen. Die hier aufgeführten Code-Beispiele waren Beispiele, die das Funktionsprinzip erläutern sollten, nicht unbedingt den tatsächlichen Einsatz der Klassen.

Wobei ich das gleich wieder etwas zurücknehmen muss: Der Beispiel-Controller im Abschnitt zur ModelAndView-Klasse war eigentlich das Beispiel, denn wesentlich mehr wird man mit Views und Resolvern in der Praxis nicht zu tun haben!

Wenn es um Views geht, findet die Arbeit wie oben gesagt in f9 eher hinter den Kulissen statt. Deshalb wird — obwohl noch wichtige Teile von f9 nicht behandelt sind — der folgende Artikel ein erstes Anwendungsbeispiel von f9 darstellen.

Downloads

Die drei hier besprochenen f9-Klassen gibt’s hier zum Download:
[download id=“6″].

Das komplette f9-Framework mit allen Klassen und Beispielen:
[download id=“12″]

3 Kommentare

  1. Pingback: Eine beispielhafte f9-Anwendungsarchitektur | FOL9000

  2. Pingback: Ein erstes Mini-Beispiel mit f9 | FOL9000

  3. Pingback: Views und Request mit dem Bean-Container verbinden | FOL9000

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.