Mein eigenes MVC-Framework – Der FrontController und das erste Projekt
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:
- Controller laden
- Aktion in Controller ausführen
- View laden
- 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 +FollowSymLinksRewriteRule 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!
Keine verwandten Beiträge gefunden.
Dieser Artikel wurde von Simon verfasst.
Gelesen: 7428x heute: 2x
Dieser Artikel wurde am Dienstag, September 23rd, 2008 um 19:30 in den Kategorien Mein MVC-Framework geschrieben. Du kannst die Kommentare über den Feed (RSS 2.0) beobachten. Du kannst eine Antwort hinterlassen, oder einen Trackback von deiner Seite setzen.



[...] Weiter zum FrontController [...]
[...] PHP, MySQL, HTML, JavaScript, AJAX, usw… « Chrome-Anteil um 0,9% gesunken Mein eigenes MVC-Framework – Der FrontController und das erste Projekt [...]
[...] Mein eigenes MVC-Framework – Der FrontController und das erste Projekt (311) [...]
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?
Hi,
das kommt alles in die Klasse FW_FrontController.
Ich hoffe, ich konnte dir helfen!
MfG
Simon