Mein eigenes MVC-Framework: Das Request- und Response-Pattern
In diesem Kapitel geht es um Anfragen und die Antwort darauf. Es gibt verschiedene Anfragearten. Die bekanntesten sind wohl GET und POST. Es gibt aber auch noch COOKIE (ja, das zählt auch als Request) und FILE. Das sind zwar noch nicht alle, aber die anderen sind uninteressant für dieses Kapitel.
Eine Antwort auf eine Abfrage enthält in der Regel sogenannte Header. Ein Header kann den Browser z.b. dazu veranlassen, Cookies zu setzen oder eine neue Datei aufzurufen („Location: …..“). Außer dem Header gibt es auch einen Body, der die eigentliche Antwort enthält.
Das alles ist im HTTP (Hypertext Transfer Protocol) festgelegt. Da das HTTP ein zustandsloses Protokoll ist, gibt es nur „kurze“ Requests, d.h. nur EINE Antwort auf EINE Anfrage. Man kann nicht 2 verschiedene Dokumente (z.B. einmal ein Bild und einmal ein HTML-Dokument) in einer Abfrage verschicken.
So, genug Theorie! Jetzt gehts an die Programmierung.
Wozu überhaupt diese Patterns?
Eigentlich könnte man auch einfach direkt auf $_GET, $_POST, … zugreifen und so die Anfrage erhalten. Das ist wahrscheinlich sogar schneller als der Umweg über ein Request-Objekt. Man hat dann aber keine besonderen Features, wie z.B. besondere Dateioperationen.
Wenn man Header senden will, muss das immer vor jeder Ausgabe geschehen. Dadurch dass die Antwort erst an den Browser geschickt wird, wenn die send()-Methode aufgerufen wird, kann man die Header aber wild verstreut im ganzen Skript senden. Und trotzdem wird es nicht zu der berühmten „headers already sent„-Meldung kommen.
Das Response-Pattern / Die Anwort
Als erstes wollen wir uns um die Antworten kümmern.
Die Grundfunktionalität (leider nicht kommentiert, ich war zu faul!):
class FW_Http_Response { private $headers = array(); private $content = ""; private $status = "200 OK"; private static $instance = null; private function __clone() {} public static function getInstance() { if(self::$instance === null) { self::$instance = new FW_Http_Response(); } return self::$instance; } public function addHeader($name, $content) { $this->headers[$name] = $content; } public function setStatus($status) { $this->status = $status; } public function addContent($content) { $this->content .= $content; } public function getContent() { return $this->content; } public function replaceContent($newContent) { $this->content = $newContent; } public function send() { header("HTTP/1.0 ".$this->status); foreach($this->headers as $name => $content) { header($name.": ".$content); } echo $this->content; //resetten $this->content = ""; $this->headers = null; } }
Die Klasse ist ein Singleton, weil es nur eine Antwort gibt.
Erklärung
Mit der Methode addHeader kann man überall Header setzen, z.B. so:
FW_Http_Response::getInstance()->addHeader("Location", "http://www.google.de");
Das würde die Seite auf Google.de umleiten.
Mit setStatus() setzt man den Status des Requests. In der Regel ist das 200 (OK). Bei Fehlern, z.B. Error 404 (Datei nicht gefunden) wäre das dann 404.
Mit addContent fügt man dann Text für die Ausgabe hinzu. Über getContent und replaceContent können später Filterklassen die Antwort nachträglich manipulieren.
Die letzte Methode send() sendet alles zusammen an den Browser. Dabei werden als erstes die Header und dann der Body gesendet.
Zusatzfunktionen erleichtern den Umgang mit der Antwort
public function redirectURL($url, $immediately = false) { $this->addHeader("Location", $url); if($immediately === true) { $this->send(); exit(); } } public function redirect($controller, $action, array $additional_params = array()) { $url = FW_Tools::getInternalUrl($controller, $action, $additional_params); $this->redirectURL($url, true); } public function setCookie($name, $value = null, $expire = null, $path = "/") { $expire = (int)($expire === null) ? time()+3600 : $expire; setcookie($name, $value, $expire, $path); } public function deleteCookie($name) { $this->setCookie($name, null, (time()-(86400*365*10))); }
redirectURL(): dient als Ersatz für das oben genannte Google-Beispiel. Wenn der 2. Parameter true ist, wird das Skript direkt verlassen und die neue Seite geladen.
redirect(): Die Methode für interne Umleitungen
setCookie(), deleteCookie() sollten sich von selbst erklären.
Das Request-Pattern / Die Anfrage
Dieses Pattern ist eine Art Proxy auf die superglobalen Arrays von PHP: $_GET, $_POST, …
Es gibt nur eine Anfrage, deshalb ist auch das wieder ein Singleton.
Erstmal ein bisschen Code zum Lesen 🙂
class FW_Http_Request { private $post; private $get; private $cookie; private $file; private $header; private $auth; private $MVC_Controller; private $MVC_Action; private static $instance = null; private function __construct() { $this->post = &$_POST; $this->get = &$_GET; $this->cookie = &$_COOKIE; $this->file = &$_FILES; foreach($_SERVER as $key => $value) { if(substr($key, 0, 5)== "HTTP_") { $key = strtolower($key); // weil es schöner aussieht $this->header[substr($key,5)] = $value; //HTTP_ abschneiden } } if(isset($_SERVER["PHP_AUTH_USER"])) { $this->auth["user"] = $_SERVER["PHP_AUTH_USER"]; $this->auth["pass"] = $_SERVER["PHP_AUTH_PW"]; } else { $this->auth = null; } } private function __clone() {} public static function getInstance() { if(self::$instance === null) { self::$instance = new FW_Http_Request(); } return self::$instance; } public function setControllerName($name) { $this->MVC_Controller = $name; } public function getControllerName() { return $this->MVC_Controller; } public function setActionName($name) { $this->MVC_Action = $name; } public function getActionName() { return $this->MVC_Action; } public function getAuthData() { return $this->auth; } public function issetHeader($key) { $key = strtolower($key); return (isset($this->header[$key])); } public function getHeader($key) { $key = strtolower($key); if($this->issetHeader($key)) { return $this->header[$key]; } return null; } public function issetGet($key) { return (isset($this->get[$key])); } public function getGet($key) { if($this->issetGet($key)) { return $this->get[$key]; } return null; } public function issetPost($key) { return (isset($this->post[$key])); } public function getPost($key) { if($this->issetPost($key)) { return $this->post[$key]; } return null; } public function issetFile($key) { return (isset($this->file[$key])); } public function getFile($key) { if($this->issetFile($key)) { return $this->file[$key]; } return null; } public function issetCookie($key) { return (isset($this->cookie[$key])); } public function getCookie($key) { if($this->issetCookie($key)) { return $this->cookie[$key]; } return null; } }
Die einzelnen Request-Arten werden hier abgelegt:
private $post; private $get; private $cookie; private $file; private $header; private $auth;
Der Konstruktor füllt diese Variablen mit den nötigen Infos.
ControllerName und ActionName
Ich finde, dass diese Informationen auch zur Anfrage gehören. Deshalb gibt es hier die 4 Methoden zum Lesen und Schreiben dieser Namen:
- getControllerName()
- setControllerName()
- getActionName()
- setActionName()
Der FrontController und (später) die ACL machen Gebrauch davon.
Für die Header, Cookies, Posts, Gets, Userdaten und Files gibt es jeweils isset- und get-Methoden. Diese sind immer gleich aufgebaut und selbsterklärend.
Was ist AuthData?
Wenn PHP die Header „PHP_AUTH_USER“ oder „PHP_AUTH_PW“ erhält, wurde im Browser diese kleine Fenster geöffnet, das Username und Passwort anfordert. In den beiden Variablen steht dann der Inhalt dieser beiden Eingabefelder. Ich verwende das immer, um meine Anwendungen während der Entwicklung vor unauthorisierten Leuten und Google zu schützen.
Als nächstes gehts an die Filter!
[…] Mein eigenes MVC-Framework: Das Request- und Response-Pattern […]
ja! das hilft mir weiter!!
freut mich 🙂
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/