Mein eigenes MVC-Framework: SubController und die Einbindung im FrontController
Im Framework ist bisher nur 1 Controller pro Seitenaufruf möglich. Es gibt aber häufig Situationen, wo man mehrere Controller auf einer Seite braucht. Ein Beispiel hierfür wäre ein Controller, der das Menü generiert. Dieser Controller wird auf jeder Seite benötigt.
Wir nennen diese neue Art von Controller SubController.
Wie auch die normalen Controller (FW_Controller) erben die SubController von FW_Controller_Base.
FW_SubController_Abstract
Jeder SubController muss von dieser Klasse erben. Andernfalls wird er vom Framework nicht verarbeitet.
abstract class FW_SubController_Abstract extends FW_Controller_Base { //--------------------------------------------- //|erbt Methode init() von FW_Controller_Base | //--------------------------------------------- /** * @access public static * @param mixed $subController * @return bool * * Überprüft den übergebenen SubController auf Gültigkeit. */ public static function isValid($subController) { return (is_object($subController) && $subController instanceof FW_SubController_Abstract && $subController instanceof FW_Controller_Base && ( is_callable(array($subController, "runBeforeMainController")) || is_callable(array($subController, "runAfterMainController")) ) ) ? true : false; } }
Die statische Methode isValid() prüft, ob ein Controller
- von FW_SubController_Abstract erbt
- von FW_Controller_Base erbt
- Mindestens eine der Methoden runBeforeMainController und runAfterMainController bietet
Nur wenn FW_SubController_Abstract::isValid($subController) true ergibt, wird er abgearbeitet.
Der Einbau in den FrontController
Der FrontController bekommt eine neue Eigenschaft:
private $subControllers = array();
Sie beinhaltet die Instanzen aller SubController.
Außerdem gibt es die Methode addSubController
public function addSubController($controllername, array $blacklist = array()) { $this->subControllers[(string)$controllername] = array("blacklist" => $blacklist, "object" => null); }
Der erste Parameter gibt den Namen des SC an. Der zweite ist ein Array, das alle Controllernamen (nicht SubController) beinhaltet, in denen dieser SubController NICHT ausgeführt werden soll.
Um die SubController nun abzuarbeiten, gibt es die runSubControllers-Methode
public function runSubControllers(FW_Http_Request $request, FW_Http_Response $response, $before_main_controller = false) { //jetzt alle Controller ausführen foreach($this->subControllers as $name => &$settings) //Referenz, weil $settings verändert wird { if(!in_array($request->getControllerName(), (array)$settings["blacklist"])) //(array): Sicher ist sicher! { //So fangen alle SubController an, weil sie im SubController-Dir liegen $name = "SubController_".$name; if($settings["object"] == null) { $instance = new $name($request, $response); $settings["object"] = $instance; } else { $instance = $settings["object"]; } if(!FW_SubController_Abstract::isValid($instance)) { throw new FW_Exception("Es ist ein invalider SubController registriert: ".$name); } if($before_main_controller) { if(method_exists($instance, 'runBeforeMainController')) { $instance->runBeforeMainController(); } } else { if(method_exists($instance, 'runAfterMainController')) { $instance->runAfterMainController(); } } } } }
Die ersten beiden Parameter muss ich wohl nicht erklären. Der dritte gibt an, welche Methode in den SubControllern aufgerufen werden soll. Ist er true, wird runBeforeMainController() aufgerufen. Andernfalls wird runAfterMainController() aufgerufen.
In der Methode wird dann geprüft, ob der aktuelle SubController den Controller auf der Blacklist hat:
if(!in_array($request->getControllerName(), (array)$settings["blacklist"]))
Danach wird der Controller instanziiert und überprüft. Falls isValid false ist, wird eine Exception geworfen:
if(!FW_SubController_Abstract::isValid($instance)) { throw new FW_Exception("Es ist ein invalider SubController registriert: ".$name); }
Am Ende wird dann noch die richtige Methode ausgeführt.
In der run-Methode des FrontControllers muß jetzt noch dafür gesorgt werden, dass die runSubControllers-Methode überhaupt ausgeführt wird. Das muß einmal so
$this->runSubControllers($request, $response, true); //runBeforeMainController
und einmal so
$this->runSubControllers($request, $response, false); //runAfterMainController
geschehen.
Die vollständige run() sieht dann also so aus:
public function run(FW_Http_Request $request, FW_Http_Response $response) { $this->preFilters->execute($request, $response); $debugger = FW_Debugger::getInstance(); $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)) { $this->runSubControllers($request, $response, true); //runBeforeMainController $controller = new $controller($request, $response); if(FW_Controller_Abstract::isValid($controller)) { try { 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 { $debugger->log("Der Controller ist nicht valid", __FILE__, __LINE__, "error"); } } else { $debugger->log("Controllerklasse nicht gefunden: ".$controller, __FILE__, __LINE__, "error"); } } else { $debugger->log("Controllerdatei nicht gefunden: ".$path."\n Eventuell ist der erste Buchstabe des Datein amens nicht groß geschrieben. ", __FILE__, __LINE__, "error"); } $this->postFilters->execute($request, $response); $response->send(); }
Das war alles, was für die SubController notwendig ist. Jetzt zeige ich noch, wie man die SubController verwendet.
SubController anwenden
Im Projektordner unter classes/SubController/ erstellen wir eine neue Datei namens General.class.php.
Es soll auf jeder Seite unserers Projekts automatisch ein Standarttitel und ein paar Meta-Tags gesetzt werden.
class SubController_General extends FW_SubController_Abstract { private $meta; private $path; private $title; public function init() { $this->meta = $this->FW_View_Helper_Meta(); $this->title = $this->FW_View_Helper_Title(); $this->path = $this->FW_View_Helper_Path(); } public function runBeforeMainController() { $this->title->set("Mein Seitentitel"); $this->path->add("Startseite", FW_Config::getInstance()->get("www_root")); $this->meta->addMetaName("author", "Simon H.") ->addMetaHttpEquiv("refresh", "5; http://localhost") ->addMeta("name", "ichBin", "Simon H."); } }
init() wird dann aufgerufen, wenn der SubController im FrontController erzeugt wird. Da wir Standartwerte setzen wollen, ist es wichtig, dass das VOR dem „Hauptcontroller“ getan wird. Deshalb nutzen wir die Methode runBeforeMainController().
So ist es jetzt möglich, dass in den eigentlichen Controllern diese Werte noch überschrieben werden.
Lasst euch nicht von den View Helpers verwirren. Die kommen noch früh genug in diesem Tutorial 😉
Nun kommen wir zum Debugger
[…] SubController und die Einbindung im FrontController […]
Das “ ? true : false“ im ersten Code ist überflüssig! 🙂
Stimmt!
Irgendwie schreibe ich das trotzdem immer hin. Ich finde, dass es so klarer wird, was zurückgegeben wird.
Aber man kann es natürlich auch weglassen.
Simon
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/