Model View Controller / MVC Pattern

Your rating: None Average: 3.2 (56 votes)

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.

  1. |-controllers/
  2. | `-ExampleController.php
  3. |-library/
  4. | |-Controller.php
  5. | `-View.php
  6. |-models/
  7. | `-ExampleModel.php
  8. |-views/
  9. | |-example/
  10. | | |-delete.tpl.php
  11. | | |-edit.tpl.php
  12. | | |-error.tpl.php
  13. | | |-index.tpl.php
  14. | | |-insert.tpl.php
  15. | | |-list.tpl.php
  16. | | `-read.tpl.php
  17. | |-footer.tpl.php
  18. | `-header.tpl.php
  19. `-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

  1. <?php
  2. class ExampleModel {
  3.  
  4. private $_storage;
  5.  
  6. public function __construct() {
  7. if (!array_key_exists('records', $_SESSION)) {
  8. $_SESSION['records'] = array();
  9. }
  10. $this->_storage = & $_SESSION['records'];
  11. }
  12.  
  13. public function create($values) {
  14. $record['title'] = $values['title'];
  15. $record['text'] = $values['text'];
  16. $this->_storage[] = $record;
  17. }
  18.  
  19. public function read($id = null) {
  20. if (null !== $id) {
  21. if(!array_key_exists($id, $this->_storage)) {
  22. throw new Exception('Datensatz existiert nicht!');
  23. }
  24. return $this->_storage[$id];
  25. }
  26. return $this->_storage;
  27. }
  28.  
  29. public function update($values) {
  30. $record['title'] = $values['title'];
  31. $record['text'] = $values['text'];
  32. $this->_storage[$values['id']] = $record;
  33. }
  34.  
  35. public function delete($id) {
  36. if (array_key_exists($id, $this->_storage)) {
  37. unset($this->_storage[$id]);
  38. return true;
  39. }
  40. throw new Exception('Kein Datensatz zum Löschen gefunden!');
  41. }
  42. }

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

  1. <?php
  2. class View {
  3.  
  4. private $_vars = array();
  5.  
  6. public function __set($key, $value) {
  7. $this->_vars[$key] = $value;
  8. }
  9.  
  10. public function render($tpl) {
  11. extract($this->_vars);
  12. include 'views/header.tpl.php';
  13. include 'views/'.$tpl;
  14. include 'views/footer.tpl.php';
  15. return ob_get_clean();
  16. }
  17.  
  18. public function display($tpl) {
  19. echo $this->render($tpl);
  20. }
  21. }

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).

  1. <?php
  2. class Controller {
  3.  
  4. public function run() {
  5. try {
  6. $controller = $this->_getController();
  7. $action = $this->_getAction();
  8. require 'controllers/'.$controller.'.php';
  9. call_user_func(array(new $controller, $action));
  10. } catch (Exception $e) {
  11. die($e->getMessage());
  12. }
  13. }
  14.  
  15. private function _getController() {
  16. $controller = isset($_REQUEST['controller'])
  17. ? strtolower($_REQUEST['controller']) : 'example';
  18. return ucfirst($controller).'Controller';
  19. }
  20.  
  21. private function _getAction() {
  22. $action = isset($_REQUEST['action'])
  23. ? strtolower($_REQUEST['action']) : 'index';
  24. return $action.'Action';
  25. }
  26. }

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.

  1. <?php
  2. class ExampleController {
  3.  
  4. private $_view;
  5.  
  6. public function __construct() {
  7. require 'library/View.php';
  8. $this->_view = new View();
  9. }
  10.  
  11. public function __call($name, $args) {
  12. // Fehlermeldung (404)
  13. $this->_view->title = 'Es ist ein Fehler aufgetreten!';
  14. $this->_view->display('example/error.tpl.php');
  15. }
  16.  
  17. public function indexAction() {
  18. $this->listAction();
  19. }
  20.  
  21. public function listAction() {
  22. require_once 'models/ExampleModel.php';
  23. $model = new ExampleModel();
  24. $this->_view->entries = $model->read();
  25. $this->_view->display('example/list.tpl.php');
  26. }
  27.  
  28. public function readAction() {
  29. if (!isset($_GET['id'])) {
  30. throw new Exception('Keine Id angegeben!');
  31. }
  32. require_once 'models/ExampleModel.php';
  33. $model = new ExampleModel();
  34. $this->_view->entry = $model->read($_GET['id']);
  35. $this->_view->display('example/read.tpl.php');
  36. }
  37.  
  38. public function createAction() {
  39. if (isset($_POST['entry'])) {
  40. require_once 'models/ExampleModel.php';
  41. $model = new ExampleModel();
  42. $model->create($_POST['entry']);
  43. $this->_view->message = 'Eintrag hinzugefuegt!';
  44. }
  45. $this->_view->display('example/insert.tpl.php');
  46. }
  47.  
  48. public function deleteAction() {
  49. if (!isset($_GET['id'])) {
  50. throw new Exception('Keine Id angegeben!');
  51. }
  52. require_once 'models/ExampleModel.php';
  53. $model = new ExampleModel();
  54. $model->delete($_GET['id']);
  55. $this->_view->display('example/delete.tpl.php');
  56. }
  57.  
  58. public function editAction() {
  59. if (isset($_POST['entry'])) {
  60. require_once 'models/ExampleModel.php';
  61. $model = new ExampleModel();
  62. $model->update($_POST['entry']);
  63. $this->_view->message = 'Eintrag bearbeitet!';
  64. }
  65. if (!isset($_GET['id'])) {
  66. throw new Exception('Keine Id angegeben!');
  67. }
  68. require_once 'models/ExampleModel.php';
  69. $model = new ExampleModel();
  70. $this->_view->entry = $model->read($_GET['id']);
  71. $this->_view->display('example/edit.tpl.php');
  72. }
  73. }

Request

Angenommen, das Beispiel wird im Document Root unter einem Ordner mvc/ installiert, könnte der Aufruf in etwa wie folgt aussehen:

  1. http://localhost/mvc/
  2. oder
  3. 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:
  1. http://localhost/mvc/?controller=example&action=list
  2. oder
  3. http://localhost/mvc/index.php?controller=example&action=list

Da "controller" und "action" im Beispiel mit Default Werten versehen werden, hätte
  1. http://localhost/mvc/?&action=list
  2. oder
  3. 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.
  1. 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.

  1. <?php
  2. require 'library/Controller.php';
  3. $controller = new Controller();
  4. $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.

AttachmentSize
mvc.tar_.gz2.43 KB

es wird doch bei jedem klick

es wird doch bei jedem klick auf einen hyperlink die index.php und somit der (front)cotroller neu ausgeführt oder nicht?

Korrekt. =)

Korrekt. =)

im exampleModel heisst die

im exampleModel heisst die funktion create und nicht insert.

public function createAction() {
if (isset($_POST['entry'])) {
require_once 'models/ExampleModel.php';
$model = new ExampleModel();
$model->create($_POST['entry']);
$this->_view->message = 'Eintrag hinzugefuegt!';
}
$this->_view->display('example/insert.tpl.php');
}

Wurde korrigiert. Danke.

Wurde korrigiert. Danke.

Is Bombe!

Is Bombe!

Schön erklärt. Danke dafür.

Schön erklärt. Danke dafür.

Re

Sehr schönes Beispiel und eine gute erklärung sowie darstellung des zusammenspiels der komponenten. vielen dank
hat mir sehr geholfen

Schön, dass es dir gefallen

Schön, dass es dir gefallen hat. Habs halt damals versucht so schmal wie möglich zu halten..

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <abbr>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.

Tags