Mein eigenes MVC-Framework – Der FrontController und das erste Projekt

<< Zurück zur Übersicht

Der FrontController ist der Controller, der entscheidet, was gemacht wird. Dazu gibt es die GET-Parameter module und action. Module bestimmt den Namen des Controllers im Projektordner (Beispiel: Startseite), Action bestimmt die Aktion, die dieser Controller ausführt (Beispiel: showNews)

Was muss der FrontController alles tun?

Bisher muss er nur entscheiden, welches Modul und welche Aktion geladen werden. Später ist er auch indirekt für die Ausgabe der Daten zuständig (Instanzierung der View)

Der FrontController ist eine Singleton-Klasse und wird in der index.php der Projekte erzeugt.

Die Anfrage routen
Wie oben geschrieben, bestimmten 2 Parameter den Ablauf der Applikation. Sie sind in der Klasse FW_Http_Request abrufbar. Diese Klasse wird später noch erklärt.

/**
   * @access public
   * 
   * Diese Methode legt fest, welches Modul und welche aktion die richtigen sind.
   */  
  public function route(FW_Http_Request $request, FW_Http_Response $response)
  {
    if($request->issetGet("module") && $request->getGet("module") != "")
    {
      $module = htmlentities($request->getGet("module"));
    }
    else
    {
      $module = "Index";
    }
    
    if($request->issetGet("action") && $request->getGet("action") != "")
    {
      $action = htmlentities($request->getGet("action"));
    }
    else
    {
      $action = "Index";
    }   
    $request->setControllerName($module);
    $request->setActionName($action);
  }

Nachdem festgelegt wurde, was getan wird, wird diese Erkenntnis dem Rest der Anwendung zur Verfügung gestellt. Und zwar wieder in FW_Http_Request.

Die Anwendung zum laufen bringen
Die run()-Methode empfängt wieder FW_Http_Request und FW_Http_Response als Parameter, um zu wissen, was angefordert (Request) wird und um etwas zurückzusenden (Response).

Der grobe Ablauf muss so aussehen:

  1. Controller laden
  2. Aktion in Controller ausführen
  3. View laden
  4. Response senden

Und hier gibts den fertigen Code, der aber schon ein bisschen mehr kann. z.B. Filter und SubController.

public function run(FW_Http_Request $request, FW_Http_Response $response)
  {
    $this->preFilters->execute($request, $response);  
    
    $module = $request->getControllerName(); 
    $action = $request->getActionName();  
    $action .= "_Action";
    
    $controllers = $this->controllerpath;
    $path = $controllers."/".FW_Controller_Abstract::getValidControllerFileName($module);  
    if(file_exists($path))
    {
        require_once($path);
        $controller = "Controller_".$module;
        if(class_exists($controller, false))
        {
            $controller = new $controller($request, $response);
            if(FW_Controller_Abstract::isValid($controller))
            {
                try
                {
                  $this->runSubControllers($request, $response, true); //runBeforeMainController
                  if(is_callable(array($controller, $action)))
                  {
                    $controller->$action();
                  }             
                  else
                  {
                    $response->redirect("Error", "error404");
                  }
                  $this->runSubControllers($request, $response, false); //runAfterMainController
                  //Display Data with View    
                  $view = FW_View::getView();
                  $view->display($response);
                }
                catch(Exception $e)
                {
                  echo $e->getMessage();
                }  
            }
            else
            {
              //ungültiger Controller
            }
       } 
       else
       {
         //Controllerklasse nicht gefunden
       }
    }
    else
    {
       //controllerdatei nicht gefunden
       die();
    }
    $this->postFilters->execute($request, $response);
    $response->send();      
  } 

Wie man sieht, wurden die else-Zweige, die bei Fehlern ausgeführt werden, noch nicht programmiert. Das muss ich bald noch nachholen. Möglich wäre hier eine Umleitung auf eine Fehlerseite oder einfach der Abbruch der Ausführung.

Controllerverzeichnis festlegen
So kann der Anwender des Frameworks selbst bestimmen, wo er seine Controller ablegen will:

/**
   * @access public
   * @param string path
   * Legt den Pfad zum Controllerverzeichnis fest        
   */  
  public function setControllerPath($path)
  {
    $this->controllerpath = $path;
  }

Ein Singleton muss es sein!
Jetzt kommt noch der übliche Code für ein Singleton.

/**
   * @access private
   * @var object instance
   * Singleton-Instanz des FrontControllers        
   */  
  private static $instance = null;

 private function __construct() {}

/**
   * @access private
   * Singleton-__clone()-Interzeptor
   */ 
  private function __clone() {}
  
  /**
   * @access public
   * @return object   
   * Liefert die Instanz der Klasse
   */ 
  public static function getInstance()
  {
    if(self::$instance === null)
    {
      self::$instance = new FW_FrontController();
    }
    return self::$instance;
  }

Es fehlen noch die Methoden für die SubController und Filter, aber das wäre jetzt zu viel auf einmal!

Das erste Projekt
In meinem Beispiel nenne ich das erste Projekt „NetDevelopers“.

Wir wissen jetzt, dass wir auf jeden Fall die Datei base_config.php aus /framework brauchen. Also können wir die index.php in /netdevelopers/www so gestalten:

error_reporting(E_ALL | E_NOTICE); //sollte während der Entwicklung so bleiben
require_once('../../framework/base_config.php');

Im Produktivbetrieb sollte error_reporting natürlich auf E_NONE gestellt sein.
Was wir sonst noch benötigen:

  • Konfigurationsobjekt
  • Autoloader
  • FrontController

Also gestalten wir unsere index.php so:

error_reporting(E_ALL | E_NOTICE);
require_once('../../../framework/base_config.php');
require_once('../autoload.php');
date_default_timezone_set('Europe/Berlin'); //wird seit PHP5 benötigt
$request =  FW_Http_Request::getInstance();
$response = FW_Http_Response::getInstance();

$conf = FW_Config::getInstance();
$conf->set("project_root", "../");
$conf->set("www_root", "http://localhost/mvc/projekte/netdevelopers/www");
$conf->set("project_classes", "../classes");
$conf->set("project_controllers", $conf->get("project_classes")."/Controller");
$conf->set("viewpath", $conf->get("project_root")."viewfiles");
$conf->readINI($conf->get("project_root")."config/config.ini");

$frontController = FW_FrontController::getInstance();
$frontController->setControllerPath($conf->get("project_controllers"));
try
{
  $frontController->route($request, $response);
  $frontController->run($request, $response);
}
catch(FW_Exception $e)
{
  echo $e->getMessage();
}

Es wird jetzt folgendes getan:

  • Base_config.php geladen
  • FW_Config erzeugt
  • wichtige Einstellungen getätigt
  • Die config.ini ausgelesen (darauf gehe ich im Kapitel zur Configklasse ein)
  • Der Projekteigene Autoloader wird geladen und registriert (siehe nächster Abschnitt)
  • Der Frontcontroller wird geladen, der Request geroutet und der Controller ausgeführt.
  • Falls eine Exception geworfen wurde, wird sie ausgegeben. (Solle nur im Testbetrieb so sein)

Schönere URLs durch mod_rewrite

Falls auf eurem Server mod_rewrite verfügbar ist, könnt ihr es dazu verwenden, die hässlichen URLs

index.php?module=Start&action=tudasunddas

so aussehen zu lassen

/Start/tudasunddas

oder so:

/Start/tudasunddas.html

In allen folgenden Artikeln gehe ich davon aus, dass mod_rewrite bei euch aktiviert ist und ihr die .htaccess richtig angelegt habt. Falls mod_rewrite bei euch nicht funktioniert, müsst ihr die Schreibweise mit den & und ? anwenden.

Aber hier erstmal unsere rewrite-rules:

RewriteEngine on
#Options +FollowSymLinks

RewriteRule index.php – [L]

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

RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^(.[^/|\.]*)/(.[^/|\.]*)\.html?$ index.php?module=$1&action=$2 [L,QSA]

Dieser Code muss in eine Datei namens .htaccess. Diese Datei muss ins www-Verzeichnis eures Projekts.

Der projekteigene Autoloader

Um den Programmieraufwand möglichst gering zu halten, verwenden Programmierer einen Autoloader. Auch in einem projekt kann zusätzlich zum FW_Autoload::load()-Autoloader ein eigener registriert werden. Die beiden kommen sich nicht in die Quere.

Ich habe für diesen Loader die prozedurale Form gewählt (aus Faulheit):

function my_autoload($className)
{
  $path = FW_Config::getInstance()->get("project_classes")."/".str_replace("_", "/", $className).".class.php";
  if(file_exists($path))
  {
    require_once($path);
  }
}
FW_Autoload::register('my_autoload');

Das ist der Inhalt der autoload.php, welche sich in /netdevelopers befindet. Ich habe die Datei absichtlich außerhalb des www-Verzeichnisses platziert. Denn dort sollen nur öffentliche Dateien wie die index.php oder Bilder/CSSs sein.

Ich hoffe, ich habe mich in diesem langen Artikel verständlich ausgedrückt. Viel Spass im nächsten Kapitel!

Weiter zum Config-Kapitel

1 Star2 Stars3 Stars4 Stars5 Stars (6 Stimme, durchschnittlich 3,50 / 5)
Loading...


10 Kommentare zu “Mein eigenes MVC-Framework – Der FrontController und das erste Projekt”

  1. […] Weiter zum FrontController […]

  2. […] PHP, MySQL, HTML, JavaScript, AJAX, usw… « Chrome-Anteil um 0,9% gesunken Mein eigenes MVC-Framework – Der FrontController und das erste Projekt […]

  3. […] Mein eigenes MVC-Framework – Der FrontController und das erste Projekt (311) […]

  4. Hey Simon, den ersten Teil dieser Seite verstehe ich nicht so ganz. Wo muss den der Code hin, der am Anfang der Seite bis „Das erste Projekt“ steht?

  5. Hi,

    das kommt alles in die Klasse FW_FrontController.

    Ich hoffe, ich konnte dir helfen!

    MfG
    Simon

  6. Wieso werden hier als Parameter für die FrontController Funktionen jeweils die Http-Objekte erwartet.
    Zum Ersten wird das Response Objekt nie in der Route Funktion verwertet (sollte es auch nicht, das ist Aufgabe der anderen Controller) und außerdem handelt es sich doch um Singleton Klassen.

    Ergo kann man auch einfach am Beginn der Funktion schreiben:
    $request = FW_Http_Request::getInstance();

    Ich gehe für mich sogar so weit das ich das in den Konstruktor des FrontControllers verlege, damit spare ich einen weiteren Funktionsaufruf.

    Oder mache ich hier einen Denkfehler?
    .-= Jan´s last blog ..NoizeMe- RT @BorowitzReport- The Rapture is really Jesuss way of defriending half the world =-.

  7. Den FrontController habe ich vor ein paar Tagen komplett neu geschreiben. Er ist jetzt deutlich kürzer und viel verständlicher geworden. Die Request- und Response- Objekte sind jetzt auch keine Singletons mehr, da man mehrere davon anlegen können soll. Dies ist für meine Implementierung des HMVC-Patterns sehr wichtig.

    Mein Framework wird übrigens bald offiziell als Beta erscheinen. Es ist HMVC-fähig und unterstütz auch nette Features wie AJAX.

    Sobald die entsprechende Seite online ist, werde ich das hier auf dem Blog ankündigen. Dann wird auch das Tutorial umgeschrieben. (bzw. es wird ein neues geben)

  8. Das klingt doch richtig gut und ich bin sehr auf das Ergebnis gespannt :).
    Allerdings bin ich persönlich nicht wirklich von HMVC überzeugt.
    Es bietet tolle Features, keine Frage, aber ich komme irgendwie nie auf ein gutes Trade-Off.
    Das tolle an MVC ist doch das es eine konsequente Trennung von Daten, Logik und Front-End etabliert und gerade diese Idee wird doch von HMVC zwingend, zumindest Teilweise kaputt gemacht, oder?

    Ich werde mir auf jeden Fall das Framework mal genauer angucken und bestimmt Teile davon in meins übernehmen :).
    .-= Jan´s last blog ..NoizeMe- Yes- Ive got my swagga back =-.

  9. Du denkst im Zusammenhang HMVC bestimmt an das APF, oder?
    Das arbeitet mit den Taglibs und die finde ich auch sehr unschön.

    In meinem Framework arbeitet man ganz normal wie mit einem MVC-Framework. Der Unterschied ist, dass man Controller (und damit auch Views) verschachteln kann. In meinen Tests hat mich die dadurch entstehende Modularität sehr überzeugt 🙂

  10. Die aktuellste Version meines HMVC-Frameworks erhaltet ihr ab sofort immer hier: http://www.net-developers.de/blog/2011/02/13/download-info-shfw-hmvc-framework-in-php/

Hinterlasse einen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

»Informationen zum Artikel

Autor: Simon
Datum: 23.09.2008
Zeit: 19:30 Uhr
Kategorien: Mein MVC-Framework
Gelesen: 49620x heute: 2x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.

»Meta