FOL9000

Ein erstes Mini-Beispiel mit f9

von

Obwohl in den bisherigen Artikel erst zwei Bereiche mit vier Klassen vorgestellt wurden, kann damit jetzt schon eine erste „echte“ Beispiel-Anwendung geschrieben werden, die alle Kriterien einer beispielhaften f9-Architektur erfüllt. Zwar macht sie noch nichts nennenswertes, aber man kann diese Beispielanwendung ohne größere Änderungen an der Architektur zu einer „echten“ Anwendung weiterentwicklen.

Erster Schritt: Die Infrastruktur

Der erste Schritt beim Erstellen der neuen Anwendung soll die Basis und Infrastruktur für f9 schaffen. Im Archiv, das es bei den Downloads gibt, liegt dazu zunächst eine Datei init_php.php. Darin wird ein eigener kleiner Autoloader gesetzt sowie ein paar php-spezifische Dinge gesetzt. Das gehört alles nicht zu f9 und soll deshalb hier auch nicht weiter erläutert werden.

Der RequestDispatcher

Damit es schnell etwas zum Ausprobieren gibt, beginnen wir mit dem RequestDispatcher bzw. mit dessen Konfiguration. Wie schon beschrieben, werden zunächst alle eingehenden Requests an ihn weitergeleitet und dann weiter an die einzelnen Controller geleitet.

Die Umleitung aller eingehenden Requests auf den RequestDispatcher geschieht in der .htaccess-Datei im Root-Verzeichnis der Anwendung. Im folgenden Beispiel werden alle Requests auf die Datei index.php umgeleitet. Je nach Installationspfad muss hier natürlich der Pfad angepasst werden.

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)\?*$ /index.php?__route__=/$1 [L,QSA]

Weiter geht es mit der Datei index.php selbst. Auch sie ist sehr unspektakulär, denn hier wird nichts anderes gemacht, als das Dispatching durch den RequestDispatcher gestartet:

require_once 'init_php.php';
require_once 'init_app.php';

use \net\f9\RequestDispatcher;

RequestDispatcher::getInstance()->dispatch();

Vor dem eigentlichen Dispatching des RequestDispatchers muss er allerdings noch konfiguriert werden. Dies wird erledigt in der Datei ini_app.php, die in Zeile zwei per require_once eingebunden wird.

use net\f9\RequestDispatcher;
use net\f9\SimpleViewResolver;

call_user_func(function() {
  $viewResolver = new SimpleViewResolver();
  $viewResolver->addNamespace('net\\f9\\examples');
  RequestDispatcher::getInstance()->setViewResolver($viewResolver);
	
  RequestDispatcher::getInstance()->
    load(dirname(__FILE__).DIRECTORY_SEPARATOR.'routes.ini');	
});

(Warum das hier als Closure realisiert wird, dazu später, im Artikel über den Dependency-Injection-Container. Hier nur soviel: Durch das Closure wird der globale Namespace nicht mit der Variablen $viewResolver vollgemüllt.)

Dem RequestDispatcher wird zunächst in den Zeilen fünf bis sieben ein View-Resolver übergeben. Für dieses Beispiel reicht die einfachste Form, der SimpleViewResolver. Damit der Resolver die Views des Beispiels auch findet, wird ihm zudem der Beispiel-Namespace bekannt gemacht (Zeile sechs). (Vgl. dazu den Artikel über die f9-View-Klassen.)

Schließlich folgt in den Zeilen neun und zehn das wichtigste, ohne das der Dispatcher seine eigentliche Arbeit nicht erledigen kann: Das Lesen der Routen-Definitionen aus der Datei routes.ini. Für das Beispiel soll nur eine Route definiert werden:

[view]
method = GET
path = "/show"
type = class
class = net\f9\examples\ExampleController
function = show

Ein GET-Request mit dem Pfad /show soll auf die Methode show() der Controller-Klasse net\f9\examples\ExampleController umgeleitet werden. Mehr brauchts erstmal nicht.

Der Controller

Nun ist alles zusammen, um Requests umzuleiten, es fehlt aber noch ein Ziel, also unser eigentlicher Controller. Der ist für unser Beispiel wieder so einfach wie möglich:

namespace net\f9\examples;

use net\f9\ModelAndView;
use net\f9\examples\UserService;

class ExampleController {
  
  private $service = null;
  
  function __construct() {
    $this->service = new UserService();
  }
	
  public function show() {
    return 
      new ModelAndView('ExampleViewBase', $this->service->read());
  }	
}

Der Controller macht nichts anderes, als Daten vom Service zu lesen
und sie zusammen mit dem Namen des Views in ein ModelAndView-Objekt zu packen, das an den Request-Dispatcher zurückgegeben wird.

Der Controller holt sich Daten vom Service, was noch fehlt ist also zunächst eben dieser.

Der Service

Der Service-Layer ist die Ebene, auf der mit der Codierung der eigentlichen Anwendung begonnen wird — und genau die ist in unserem Beispiel irrelevant bzw. gar nicht vorhanden. Der Service gibt deshalb immer nur ein Array mit ein paar Fake-Daten zurück.

namespace net\f9\examples;

class UserService {
  public function read() {
    return array('firstname' => 'Fritz', 'lastname'  => 'Meier');
  }
}

Der View

Da wir im Request-Dispatcher den SimpleViewResolver nutzen (s.o.) ist der Name des Views der Name einer View-Klasse. Entsprechend folgt als nächstes die Klasse ExampleViewBase (vgl. Zeile 16 im Controller):

namespace net\f9\examples;

use net\f9\View;

class ExampleViewBase extends View {
	
  protected $basedir = __DIR__;
  protected $file = 'test-template.php';
	
  public function render() {
    parent::render($data);
		
    $this->main   = 'test-main.php';
    $this->header = 'test-header.php';
    $this->footer = 'test-footer.php';	
    parent::render();
  }	
}

Um das Beispiel nicht ganz so langweilig werden zu lassen gibt’s beim View eine kleine, eigentlich nicht notwendige Besonderheit. In kaum einer echten Anwendung wird man jede Seite komplett auf der Grundlage einer einzelnen HTML- oder php-Datei entwickeln. In der Regel wird man stattdessen für die verschiedenen Teile einer Seite — vor allem für die immer gleich bleibenden — je eigene Dateien vorsehen. Typische Beispiele für solche auf allen Seiten eingesetzten Teile sind etwas Header und Footer. Und so soll auch hier der View nicht aus einer einzigen Datei, sondern aus Header, Hauptteil und Footer bestehen, alle in einer je eigenen Datei. Um zu zeigen, dass man in allen drei Seitenteilen auf die Modell-Variablen zugreifen kann, sollen sie im Beispiel auch in allen Teilen angezeigt werden.

Los geht es mit der ersten Datei, die keine andere Funktion hat, als die anderen einzubinden. Welche Dateien das genau sind, das wurde oben im View festgelegt und in den Variablen $header, $main und $footer festgelegt. Gäbe es in unserem Beispiel CSS- oder JavaScript-Dateien, würden sie hier eingebunden, aber das Beispiel soll so einfach wie möglich bleiben.

<html>
<head></head>
<body>

<?php include $header; ?>

<?php include $main; ?>

<?php include $footer; ?>

</body>
</html>

Header und Footer sind simpel, in ihnen soll nur der Vorname des Users angezeigt werden (der im Service unter dem Namen firstname zum Modell hinzugefügt wurde). Der Einfachheit halber sind Header und Footer weitestgehend gleich.

Zunächst also der Header:

<p><?php echo $firstname ?>'s header</p>

Und der Footer:

<p><?php echo $firstname ?>'s footer</p>

Bleibt nur noch der wichtigste Teil der Seite, der Hauptteil. In ihm werden schlicht alle Modell-Variablen in einer Tabelle dargestellt:

<p>This is <?php echo $firstname ?>'s test.</p>
<p>Model variables:</p>
<table>
<?php foreach ($__model as $key=>$value): ?>
  <tr>
    <td> <?php echo $key ?> </td>  <td> <?php echo $value ?> </td>
 </tr>
<?php endforeach; ?>
</table>

(Wir erinnern uns: in der Variablen $__model steht im View das komplette Modell als assoziatives Array zur Verfügung.)

Schluss

Sicher, der Service war reiner Fake, aber der f9-spezifische Rest in diesem Beispiel war schon eine ausbaufähige, komplette f9-Anwendung. Dafür reichten drei Klassen, ein paar View-Dateien (die man auch zu einer hätte zusammenfassen können) und ein paar Änderungen bzw. Angaben in der Konfiguration. Wollte man aus diesem Beispiel eine echte Anwendung in größerem Rahmen bauen, würde dies tatsächlich ein Ausbauen und Erweitern bedeuten, Änderungen an dem bisher geschriebenen wären wenn überhaupt nur in geringem Maße erforderlich.

Außerdem hat sich in diesem ersten kleinen ‚Praxis‘-Test gezeigt, dass die Kriterien, die ich selbst für Micro-Frameworks aufgestellt habe, mit f9 erfüllt werden können.

Mit den jetzt noch folgenden f9-Klassen wird die Anwendung komplett werden: Databank-Klasse und Session-Management werden als nächstes behandelt. Vor allem aber wird der Dependency-Injection-Container vorgestellt werden, mit dem das ganze erst richtig rund wird.

Kommentare sind geschlossen.