Mein eigenes MVC-Framework: Die View-Klassen(n)

<< Zurück zur Übersicht

Jetzt kommen wir zu einem wirklich wichtigen und interessanten Teil. Bisher konnten wir nämlich noch gar nichts ausgeben, ohne das EVA-Prinzip (Eingabe – Verarbeitung – Ausgabe) zu verletzen. Es soll jetzt eine weitere Schicht des MVC-Patterns implementiert werden, die View-Schicht!

Anforderungen

Mir war wichtig, dass es verschiedene Ausgabeformate geben kann. Bisher habe ich zwar nur HTML benötigt, aber dank der abstrakten Architektur könnte ich auch problemlos JSON, XML oder PDF ausgeben lassen. Es gibt eine View-Factory, die die richtige View-Klasse instanziiert und das Objekt zurückgibt:

final class FW_View
{    
   /**
    * @access public static
    * @return object    
    * 
    * Diese Methode soll das richtige View-Objekt liefern. Es dient also als
    * Factory für die Views. Das soll dazu führen, dass der Programmierer nicht
    * wissen muss, mit welchem View-Objekt er arbeitet.                
    */       
   public static function getView()
   {
     return call_user_func_array(array(FW_FrontController::$view,'getInstance'),array());
   }
}


Man kann jetzt überall im kompletten Projekt mit FW_View::getView() mit dem View-Objekt arbeiten.
Wie man sieht, ist es erforderlich, dass alle View-Klassen die getInstance implementieren. Es ist zwar nicht zwingend notwendig, dass sie auch ein Singleton sind (getInstance() könnte genausogut auch bei jedem Aufruf ein Objekt erzeugen), aber wer braucht schon mehrere Views? Mir würde da jetzt kein Sinn einfallen.

Alle Views müssen das FW_View_Interface implementieren, das so aussieht:

interface FW_View_Interface
{   
  public function display(FW_Http_Response $response);
  public static function getInstance();
}

Die Methode display() wird aufgerufen, wenn die Ausgabe gemacht werden soll.

Es gibt noch die abstrakte Klasse FW_View_Abstract, von der jede View erben muss. Damit implementiert sie automatisch das FW_View_Interface.

abstract class FW_View_Abstract implements FW_View_Interface
{
   public static $layout = "Standart";
   protected $templates = array();
   protected $templateNow = null;
   
   protected $viewHelpers = array();
   
   protected static $instance = null;
   
   protected function __clone() {}
   protected function __construct() {}   
   
   //------------------------------------------
   // getInstance() kann nicht in der abstrakten Klasse definiert werden,
   // weil dazu der Klassenname bekannt sein muss.
   // self zeigt immer auf FW_View_Abstract. Das wird mit PHP 6 aber endlich
   // funktionieren.
   
   /**
    * @access public
    * @param string $func
    * @param array  $params
    * 
    * Aufruf von ViewHelpers.
    * Wenn der Name des helpers mit FW_ beginnt, ist der Helper ein Teil des
    * Frameworks. Deshalb wird FW_View_Helper_ als Prefix benutzt.
    * Ansonsten ist das Prefix einfach ViewHelper.                            
    */       
   public function __call($func, $params)
   {
       if(substr($func, 0, 3) == "FW_")
       {
         $class = "FW_View_Helper_".substr($func, 3);
       }
       else
       {
         $class = "ViewHelper_".$func;
       }
       
       if(!isset($this->viewHelpers[$class]))
       {
         $this->viewHelpers[$class] = new $class;
       }       
       
       return $this->viewHelpers[$class]->run($params);
   }

In der statischen Eigenschaft $layout wird definiert, welches Seitenlayout angezeigt werden soll. Was genau das ist, wird später erklärt.

Die magische Methode __call habe ich hier zum einfacheren Verwenden von ViewHelpern geschrieben.

Die wichtigste Klasse ist aber FW_View_HTML. Sie wird zum Erzeugen von HTML-Dokumenten benutzt.

class FW_View_HTML extends FW_View_Abstract
{
   public static function getInstance()
   {
     if(self::$instance === null)
     {
        self::$instance = new self();
     }
     return self::$instance;
   }
   
   public function display(FW_Http_Response $response)
   {
      ob_start();
      $path = FW_Config::getInstance()->get("viewpath")."/layouts/".self::$layout.".tpl.php";
      if(file_exists($path))
      {
        include($path);
        $content = ob_get_clean();
        $response->addContent($content);
      }
      else
      {
        $debugger = FW_Debugger::getInstance();
        $debugger->log("Layout kann nicht gefunden werden", __FILE__, __LINE__, "error");
      }
   }
   
   public function addTemplateContent($templatename, $content, $templatefile)
   { 
     $this->templates[$templatename] = array($content, $templatefile);
   }
      
   public function __get($key) //darf nur vars aus aktuellem tplcontent zeigen
   {
     if(isset($this->templates[$this->templateNow][0][$key]))
     {
        return $this->templates[$this->templateNow][0][$key];
     }
   }
   
   public function showTemplateContent($templatename)
   {  
     $this->templateNow = $templatename;   
     if(isset($this->templates[$this->templateNow]))
     {
      include(FW_Config::getInstance()->get("viewpath")."/templates/".$this->templates[$this->templateNow][1]);
     }
     else
     {
      echo "Das Template ".$templatename." wurde von keinem Controller mit Daten gefuettert!";
     }
   }
   
   public function includeTemplateFile($templatename)
   {
     $path = FW_Config::getInstance()->get("viewpath")."/static_templates/".$templatename.".tpl.php";
     if(file_exists($path))
     {
      include($path);
     } 
     else
     {
      echo "Template nicht gefunden: ".$templatename." in ".$path;
     }
   }
}

Erklärung der einzelnen Methoden
display()

   public function display(FW_Http_Response $response)
   {
      ob_start();
      $path = FW_Config::getInstance()->get("viewpath")."/layouts/".self::$layout.".tpl.php";
      if(file_exists($path))
      {
        include($path);
        $content = ob_get_clean();
        $response->addContent($content);
      }
      else
      {
        $debugger = FW_Debugger::getInstance();
        $debugger->log("Layout kann nicht gefunden werden", __FILE__, __LINE__, "error");
      }
   }

Display() ist die Methode, die später vom FrontController aufgerufen wird, wenn er mit der Abarbeitung aller Controller fertig ist und die Antwort an den Client senden will.

Es wird die Layout-Datei geöffnet und die Ausgabe in $content abgespeichert. Die Layout-Datei ist die Datei, die das Layout der Seite ohne Inhalt enthält. Der eigentliche Inhalt wird dann in dieser Datei dynamisch geladen.

addTemplateContent()

public function addTemplateContent($templatename, $content, $templatefile)
   { 
     $this->templates[$templatename] = array($content, $templatefile);
   }

Hiermit kann man im Controller Daten zur Ausgabe abspeichern.

__get()

 public function __get($key) //darf nur vars aus aktuellem tplcontent zeigen
   {
     if(isset($this->templates[$this->templateNow][0][$key]))
     {
        return $this->templates[$this->templateNow][0][$key];
     }
   }

Im einzelnen Templatefile kann über $this->irgend_eine_variable auf die Variablen zugegriffen werden, die vorher mit addTemplateContent definiert wurden. Da es mehrere Templates geben kann, dient $this->templateNow als Pointer auf das aktuelle Template.

$this->templateNow wird immer dann gesetzt, wenn showTemplateContent() aufgerufen wird

public function showTemplateContent($templatename)
   {  
     $this->templateNow = $templatename;   
     if(isset($this->templates[$this->templateNow]))
     {
      include(FW_Config::getInstance()->get("viewpath")."/templates/".$this->templates[$this->templateNow][1]);
     }
     else
     {
      echo "Das Template ".$templatename." wurde von keinem Controller mit Daten gefuettert!";
     }
   }

Ein kleines Beispiel:
Es gibt 3 Templates: Content, Menu, Shoutbox

TemplateNow zeigt nach showTemplateContent(„content“) auf „content“ und stellt die Daten dar, die vorher mit addTemplateContent(„content“, $data, „content.tpl.php“) definiert wurden.
TemplateNow zeigt nach showTemplateContent(„Menu“) auf „Menu“ und stellt die Daten dar, die vorher mit addTemplateContent(„Menu“, $data, „Menu.tpl.php“) definiert wurden.
TemplateNow zeigt nach showTemplateContent(„Shoutbox“) auf „Shoutbox“ und stellt die Daten dar, die vorher mit addTemplateContent(„Shoutbox“, $data, „Shoutbox.tpl.php“) definiert wurden.

Anwendung
Als erstes legen wir im Projektordner unter viewfiles/layouts die Datei Standart.tpl.php an. Diese Datei muss in jedem Projekt vorhanden sein! Der Inhalt dieser Datei sieht zu Testzwecken ganz einfach aus:

Der Inhalt der Seite ist: 
< ?php $this->showTemplateContent("content"); ?>

Auch eine Templatedatei wird benötigt. Diese Datei wird jetzt einfach unter viewfiles/templates/content.tpl.php erstellt. Als Inhalt verwenden wir wieder was ganz schlichtes:

< ?=$this->text; ?>

Wenn man das Skript jetzt aufruft, erscheint kein Inhalt. Warum? Weil „Text“ noch nicht definiert wurde!

Das wird nämlich im Controller gemacht:

class Controller_Index extends FW_Controller_Abstract
{
  public function Index_Action()
  {
    FW_View::getView()->addTemplateContent("content", array("text" => "Es geht!"), "content.tpl.php");
  }
}

Und jetzt geht es! Ich hoffe, auch dieses Kapitel wurde ausführlich genug beschrieben. Wenn nicht, bin ich in den Kommentaren für euch da!

1 Star2 Stars3 Stars4 Stars5 Stars (3 Stimme, durchschnittlich 5,00 / 5)
Loading...


15 Kommentare zu “Mein eigenes MVC-Framework: Die View-Klassen(n)”

  1. […] Mein eigenes MVC-Framework: Die View-Klassen(n) […]

  2. Hi Simon,

    eine Frage:

    Wie kann ich das Standard Layout ändern?

    Da hab‘ ich noch nicht so ganz durchgeblickt..

    Danke & Gruß
    Magic

  3. Hi

    in der Datei View/Abstract.class.php befindet sich eine Konstante
    public static $layout = „Standart“;

    Diese Konstante gibt an, welches Layout geladen wird. Wenn man FW_View_HTML::$layout bzw. FW_View_Abstract::$layout mit einem anderen Wert überschreibt, z.B. mit „MeinDesign“, versucht mein Framework, die Template-Datei viewfiles/layouts/MeinDesign.tpl.php zu laden.

    Ich hoffe, ich konnte deine Frage beantworten?

    Du hast recht, dass das nicht so klar hervorgeht. Eine Methode zum Ändern des Layouts wäre angebracht. Ich denke darüber nach!

    Danke fürs Testen übrigens 😉
    Wie gefällt dir das FW ansonsten?

    Simon

  4. Vielen Dank für Deine schnelle Antwort.

    Vielleicht wäre eine Method hierfür echt nicht übel…

    Mir gefällt Dein Framework bis jetzt echt sehr gut. Hat ein bissl gedauert, bis ich mich eingearbeitet habe – aber das ist ja völlig normal.

    Magic

  5. Freut mich, wenn es dir gefällt 🙂

    Die Methode ist bereits programmiert und kommt dann in der neuen Version hier mit rein.

    Verwendest du denn das FW in einem deiner Projekte? 😉

    Simon

  6. Fein, da bin ich gespannt!

    Ich denke, ich werde das FW später in eigenen Projekten nutzen.

    Ich möchte keines der existierenden FWs nutzen. Ich finde, sie sind teilweise zu überladen.
    Ausserdem probiere ich gern das Eine oder Andere aus. Bei Deinem FW war ich von Anfang an dabei und kann dabei sehen wie es entsteht. Das gefällt mir.

    Beste Grüße
    Magic

  7. 🙂
    Genau aus diesem Grund (die fertigen sind viel zu vollgestopft mit unnötigen funktionen) habe ich auch meine eigenes Framework programmiert. Bzw bin noch dabei 😉

    Simon

  8. Hallo!

    Nettes FW was du da gebastelt hast!
    Was mich wirklich beschäftigt:

    Warum der Umweg über die Layout-Datei?
    In dieser Datei kann ich keine Platzhalter setzen!?
    Da ich mit CSS+DIV Containern arbeite würde ja dann nicht viel im Layout stehen, außer dem Doctype und den HTML-Standarts wie html, head, body.
    Selbst der Titel der Seite und stylesheet pfade müssten dann ein eigenes Template sein? Oder habe ich da etwas falsch verstanden?

    Man könnte doch auch Template-Dateien in Template-Dateien laden!?

    Grüße
    Sepp

  9. Hi,

    danke!

    Die Layout-Datei ist dafür da, Inhalte zu speichern, die auf jeder Seite vorkommen. Also die Grundstruktur.
    Mithilfe der Templates (statisch/dynamisch) kann man dann einzelne Teile der Seite verändern. Die Inhalte für die Templates werden vom Controller bzw. SubController generiert.

    Bei mir steht in der Layout-Datei ganz oben der Doctype, danach kommt das HTML-Gerüst mit Einbindung der CSS, JS, … und natürlich dem eigentlichen Layout (Div fürs Menü, …)
    An den entsprechenden Stellen binde ich dann die eigentlichen Inhalte über die Templates ein.

    Ich habe jetzt nicht getestet, ob das geht, aber theoretisch müsste man Templates auch verschalteln können.

    Achso: Den Titel der Seite habe ich über einen ViewData-Helper realisiert. So kann ich den Titel an jeder Stelle im Programmcode (außer in den Models) manipulieren und auslesen. (So ähnlich wie bei Zend)
    Auch die Meta-Tags werden bei mir so generiert.

    Leider kann ich in den Kommentaren keinen Code posten, sonst würde ich dir mal eine Layoutdatei von mir zeigen.

    Übrigens ist es auch sehr einfach möglich, einer Seite mehrere Aussehen zu verpassen, indem man einfach mehrere Layouts mit anderen Divs anlegt. Das Layout kann man über die View-Klasse angeben. (Konstante FW_View::$layout ist es glaube ich)

    Ich hoffe, ich konnte dir damit helfen!

    MfG
    Simon

  10. […] Die neuesten Kommentare Simon commented on Mein eigenes MVC-Framework: Die View-Klassen(n) […]

  11. Danke für die ausführliche Erklärung!
    So 100%ig bin ich noch nicht durchgestiegen… 😉

    Ich warte gespannt auf weitere Beiträge bzw. überarbeitete Versionen!

    Großes Lob! Aus Erfahrung weiß ich das eine Dokumentation meist mehr Arbeit ist als das Programmieren selbst. 🙂

  12. Was mich grundsätzlich interessiert ist, wie es gehen würde, Smarty in das Framework zu integrieren. Gibt es dazu einen Ansatz?

  13. Im Grunde musst du nur eine eigene View-Klasse schreiben, die alle Daten 1:1 an Smarty weiterleitet. Finde ich aber sinnlos… Wozu brauchst du Smarty?

  14. Viele User / Kunden etc. finden die Smarty syntax eben sehr einfach und kennen es bereits sehr gut. Bin derzeit am überlegen, einige Dinge wieder auf meine Art zu machen. Auch wenn ich nicht gerade ein Anfänger bin, ist Deine Doku leider sehr lückenhaft, was ich ebenso schade wie umständlich finde, da es wirklich Potential hat!

  15. 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/

Hinterlasse einen Kommentar!

Time limit is exhausted. Please reload the CAPTCHA.

»Informationen zum Artikel

Autor: Simon
Datum: 24.02.2009
Zeit: 01:36 Uhr
Kategorien: Mein MVC-Framework
Gelesen: 21570x heute: 2x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.

»Meta