Die Model View Controller Architektur gehört zu den aufwendigsten und bekanntesten Entwurfsmustern. Dies ist vielleicht auch der Grund warum ich mich lange gescheut habe einen Artikel zu diesem Thema zu verfassen. Ich hoffe daher im Folgenden eine kurze, aber verständliche Beschreibung bieten zu können.
Zudem wird anhand einer sehr simplen Implementatirung veranschaulicht, wie der Model View Controller funktioniert.
Die Komponenten
Der MVC verleiht im Gegensatz zu den meisten Design Patterns einer Anwendung ein komplette Struktur unterteilt in die folgenden drei Komponenten:
- Model
- Das Model stellt die Abstraktion auf einen Datenbestand dar. In den meisten Fällen wird dies eine Datenbank sein. Dies ist aber keine Voraussetzung. Beispielsweise könnte die Datenbasis auch aus XML-Dateien oder einem Service bestehen.
- View
- Die View übernimmt die Präsentation bzw. Ausgabe der Anwendung. In einer PHP-basierten Anwendung wären dies zum Beispiel eine Template Engine wie Smarty und die zugehörigen Templates.
- Controller
- Wie der Name schon sagt, steuert der Controller die Anwendung. Der Request, bzw. die Anfrage wird durch den Controller an eine Methode übergeben. Diese lädt dann anhand der Parameter das (Daten-)Model und übergibt dessen Rückgabe an das entsprechende Template.
Das Prinzip ist somit eigentlich ganz einfach. Die Anwendung wird sauber in die erwähnten Komponenten unterteilt. Eine übersichtliche Strukturierung wird somit durch Wartbarkeit und Erweiterbarkeit ergänzt.
Häufig werden mehr oder weniger komplexe Anwendung unbewusst durch eine Grundstrukur angetrieben welche man in gewisser Weise dem Model View Controller Pattern zuordnen könnte.
Vereinfachte, beispielhafte Implementation
Das Pattern kann eine enorme Komplexität erreichen. Trotzdem werde ich versuchen eine simple Beispielanwendung vorzustellen, welche dem besseren Verständnis des MVCs dienen soll.
Als Praxisbezug habe ich mich für das Handling von Datensätzen, wie aus einem Blogsystem bekannt, entschieden. (Einträge anzeigen, erstellen, bearbeiten und löschen.)
Verzeichnis- und Dateistruktur
Die zu verwendende Verzeichnisstruktur spiegelt die einzelnen Komponenten wider. Hinzu kommt im Beispiel ein weiteres Verzeichnis library/ zur Ablage von global verwendeten Hilfsklassen.
|-controllers/ | `-ExampleController.php |-library/ | |-Controller.php | `-View.php |-models/ | `-ExampleModel.php |-views/ | |-example/ | | |-delete.tpl.php | | |-edit.tpl.php | | |-error.tpl.php | | |-index.tpl.php | | |-insert.tpl.php | | |-list.tpl.php | | `-read.tpl.php | |-footer.tpl.php | `-header.tpl.php `-index.php
Model
Das Model wird im Beispiel eine einfache Abstraktion zum Zugriff auf die Datensätze abbilden. Um das Beispiel einfach zu halten, wird die Datenbasis nicht etwa eine Datenbank sein, sondern durch die Session ($_SESSION) simuliert.
Im Konstruktor wird die Verfügbarkeit der Session sichergestellt und anschließend durch eine Klassenvariable referenziert. Hinzu kommen die vier Methoden nach dem CRUD-Prinzip (Create, Read, Update, Delete).
ExampleModel Klasse
<?php class ExampleModel { private $_storage; public function __construct() { session_start(); if (!array_key_exists('records', $_SESSION)) { $_SESSION['records'] = array(); } $this->_storage = & $_SESSION['records']; } public function create($values) { $record['title'] = $values['title']; $record['text'] = $values['text']; $this->_storage[] = $record; } public function read($id = null) { if (null !== $id) { if(!array_key_exists($id, $this->_storage)) { throw new Exception('Datensatz existiert nicht!'); } return $this->_storage[$id]; } return $this->_storage; } public function update($values) { $record['title'] = $values['title']; $record['text'] = $values['text']; $this->_storage[$values['id']] = $record; } public function delete($id) { if (array_key_exists($id, $this->_storage)) { unset($this->_storage[$id]); return true; } throw new Exception('Kein Datensatz zum Löschen gefunden!'); } }
View
Wie in der Einleitung dieses Artikels bereits erwähnt, stellt die View die Präsentation der Anwendung zur Verfügung. Im Beispiel sind dies eine "Mini-Template-Engine" mit der Klasse View im Ordner library/ und natürlich das Verzeichnis der Templates. Dieses bekommt für jeden Controller ein Unterverzeichnis in welchem sich die Template-Dateien befinden. Im Beispiel ist es ein Ordner example/.
View Klasse
<?php class View { private $_vars = array(); public function __set($key, $value) { $this->_vars[$key] = $value; } public function render($tpl) { extract($this->_vars); ob_start(); include 'views/header.tpl.php'; include 'views/'.$tpl; include 'views/footer.tpl.php'; return ob_get_clean(); } public function display($tpl) { echo $this->render($tpl); } }
Controller
Die Controller-Komponente besteht im wesentlichen aus zwei Teilen. Diese sind der eigentliche Controller - häufig auch als Front-Controller bezeichnet - und die Controller-Klassen. Der Front-Controller nimmt die Anfrage entgegen und ermittelt daraus, welche Controller-Klasse und Methode daraufhin aufzurufen ist. Die Controller-Klassen stellen diese Methoden und somit die Funktionalitäten der Anwendung bereit.
Controller Klasse
Die Controller-Klasse ermittelt anhand der Anfrage ($_REQUEST) den zu instantiierenden Controller und die aufzurufende Methode (Action).
<?php class Controller { public function run() { try { $controller = $this->_getController(); $action = $this->_getAction(); require 'controllers/'.$controller.'.php'; call_user_func(array(new $controller, $action)); } catch (Exception $e) { die($e->getMessage()); } } private function _getController() { $controller = isset($_REQUEST['controller']) ? strtolower($_REQUEST['controller']) : 'example'; return ucfirst($controller).'Controller'; } private function _getAction() { $action = isset($_REQUEST['action']) ? strtolower($_REQUEST['action']) : 'index'; return $action.'Action'; } }
ExampleController Klasse
Die einzige Controller-Klasse im Beispiel ist der ExampleController. Dieser stellt die möglichen Aufrufe bereit. Jede über die Controller-Logik aufrufbare Methode endet auf den Namen -Action um zu vermeiden, dass alle Methoden der Klasse aufgerufen werden können. Die Implementierung der magischen Methode __call() sorgt für eine Fehlerbehandlung beim Aufruf einer nicht-existenten Action-Methode.
Die einzelnen Action Methoden behandeln nun mit Hilfe des Models (Datenabstraktion) die notwendigen Datensätze und geben diese oder auch andere Daten an die im Konstruktor erstellte View (Ausgabe) weiter. Der Aufruf von $this->_view->display('xxx.tpl.php') teilt der View mit, die Daten mittels des übergebenen Templates zu verarbeiten und auszugeben.
Jetzt sollte erkenntlich werden, dass die Controller die Anfrage verarbeiten und das Datenmodell verändern und eine Ausgabe erzeugen.
<?php class ExampleController { private $_view; public function __construct() { require 'library/View.php'; $this->_view = new View(); } public function __call($name, $args) { // Fehlermeldung (404) $this->_view->title = 'Es ist ein Fehler aufgetreten!'; $this->_view->display('example/error.tpl.php'); } public function indexAction() { $this->listAction(); } public function listAction() { require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $this->_view->entries = $model->read(); $this->_view->display('example/list.tpl.php'); } public function readAction() { if (!isset($_GET['id'])) { throw new Exception('Keine Id angegeben!'); } require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $this->_view->entry = $model->read($_GET['id']); $this->_view->display('example/read.tpl.php'); } public function createAction() { if (isset($_POST['entry'])) { require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $model->insert($_POST['entry']); $this->_view->message = 'Eintrag hinzugefuegt!'; } $this->_view->display('example/insert.tpl.php'); } public function deleteAction() { if (!isset($_GET['id'])) { throw new Exception('Keine Id angegeben!'); } require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $model->delete($_GET['id']); $this->_view->display('example/delete.tpl.php'); } public function editAction() { if (isset($_POST['entry'])) { require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $model->update($_POST['entry']); $this->_view->message = 'Eintrag bearbeitet!'; } if (!isset($_GET['id'])) { throw new Exception('Keine Id angegeben!'); } require_once 'models/ExampleModel.php'; $model = new ExampleModel(); $this->_view->entry = $model->read($_GET['id']); $this->_view->display('example/edit.tpl.php'); } }
Request
Angenommen, das Beispiel wird im Document Root unter einem Ordner mvc/ installiert, könnte der Aufruf in etwa wie folgt aussehen:
http://localhost/mvc/ oder http://localhost/mvc/index.php
Um nun eine bestimmte Action-Methode aufzurufen müssen Controller und Action als Parameter in der Request-Query angegeben werden:
http://localhost/mvc/?controller=example&action=list oder http://localhost/mvc/index.php?controller=example&action=list
Da "controller" und "action" im Beispiel mit Default Werten versehen werden, hätte
http://localhost/mvc/?&action=list oder http://localhost/mvc/index.php?&action=list
den gleichen Effekt wie der Aufruf zuvor, da der Standardwert für den Controller auf "example" zeigt.
Nach diesem Prinzip können nun alle verfügbaren Methoden der Controller angesteuert werden. Mit Hilfe des Apache Moduls
mod_rewrite könnten die Aufrufe eleganter und suchmaschienenfreundlicher gestaltet werden, wie man es aus vielen Anwendungen kennt.http://localhost/mvc/example/list
index.php
Zu guter Letzt noch die index.php. Diese wird sehr schmal sein, der die Controller Logik in eine eigene Klasse verschoben wurde (library/Controller.php). Somit enthält diese Datei lediglich das Einbinden und Instantiieren des Controllers und den anschließenden Aufruf der Methode run() welche die Applikation bzw. den Model View Controller startet.
<?php require 'library/Controller.php'; $controller = new Controller(); $controller->run();
Fazit
Der Model View Controller stellt eine Möglichkeit dar die Komponenten der Ausgabe, Datenhaltung und Anwendungslogik getrennt zu halten. Mir persönlich gefällt dieses Prinzip sehr gut. Auch die meisten bekannten größeren Framework kommen nicht mehr ohne einer MVC-Implementierung daher. Wer tiefer in diese Thematik einsteigen will, dem kann ich die Umsetzung im Zend Framework sehr empfehlen. Die dortige Controller Logik ist sehr komplex und mit sehr vielen zusätzlichen Features ausgestattet (Routing, mod_rewrite, Request-Abstraktion).
Ich hoffe mit diesem Artikel einen verständlichen Einstieg in das MVC-Prinzip liefern zu können. Wer noch Ideen oder Verbesserungen hat, kann mir diese gern in einem Kommentar mitteilen.
Beispielanwendung
Die im Artikel beschriebene Beispielanwendung werden ich zum Download beilegen. Zum Einsatz ist jedoch nicht geeignet, da sie lediglich das MVC-Pattern veranschaulichen und demonstrieren soll. Auf eine Validierung und saubere Verabreitung der Eingaben wurde daher zum Beispiel verzichtet. Die dortigen Funktionalitäten (siehe CRUD) haben bei mir funktioniert.
| Attachment | Size |
|---|---|
| mvc.tar_.gz | 2.43 KB |


Recent comments
3 weeks 4 days ago
4 weeks 15 hours ago
20 weeks 3 days ago
26 weeks 2 days ago
26 weeks 2 days ago
27 weeks 2 days ago
27 weeks 2 days ago
48 weeks 1 day ago
49 weeks 1 day ago
1 year 31 weeks ago