MVC-Framework: Große Änderung bei Controllers und ViewHelpers

So ist das halt in der Entwicklung: Man hat ein bestehendes Konzept, will etwas neues hinzufügen und stellt fest, dass das bisherige nur teilweise damit zusammenarbeiten wird.

Also bleibt nichts anderes übrig als die Anpassung des bisherigen Codes. Bei mir war das jetzt nicht soo schlimm. Es betrifft die SubController, Controller und ViewHelper.

Um was geht es überhaupt?

Es geht um die ViewHelper, die in der Lage sein sollen, Meta-Tags, Seitentitel, usw. ausgeben zu können. Dazu müssen sie irgendwoher die Daten bekommen. Das soll nicht nur con den Controllern möglich sein, sondern auch von den SubControllern. Genau hier tritt das Problem auf.

Ich möchte nämlich die magische Methode __call() von PHP5 verwenden. Und doppelter Code ist ja bekanntlich eine OOP-Sünde. Deshalb erben jetzt FW_Controller_Abstract und FW_SubController_Abstract beide von FW_Controller_Base, die so aussieht:

< ?php
/**
 * @licence See: /licence.txt
 */ 

/**
 * @author Simon H.
 * @version 0.1
 * @package MVC-Framework  
 * 
 * FW_Controller_Base ist die Grundlage für alle
 *   FW_SubController und
 *   FW_Controller    
 * 
 * Sie müssen von dieser Klasse erben  
 * Hier werden die wichtigsten Objekte in Eigenschaften ablegelegt:
 * - FW_Http_Request    $request
 * - FW_Http_Response   $response
 * - FW_View            $view
 * 
 * Diese Objekte sind in jedem Controller / SubController verfübar.
 * Der Konstruktor dieser Klasse wird finalisiert, d.h. er kann in keiner 
 * Kindklasse überschrieben werden.
 * Deshalb gibt es einen Ersatzkonstruktor namens init()
 * Er wird von FW_Controller_Base::__construct() aufgerufen           
 */ 
abstract class FW_Controller_Base
{
  /**
  * @var request
  * Speichert die Anfrage an den Webserver in einem Objekt
  * 
  * ist in jedem Controller unter $this->request erreichbar      
  */    
 protected $request;

 /**
  * @var response
  * Speichert die Antwort des Webservers an den Client in einem Objekt
  * 
  * ist in jedem Controller unter $this->response erreichbar      
  */  
 protected $response;

 /**
  * @var view
  * Speichert das Objekt, das zum Darstellen der Seite verwendet wird 
  * 
  * ist in jedem Controller unter $this->view erreichbar      
  */  
 protected $view;

 /**
  * @var ViewDataHelpers
  * Hier kommen alle ViewDataHelpers rein
  * So werden sie nur einmal instanziiert (spart Speicher)      
  */
 protected $viewDataHelpers = array();

 /**
  * @access public
  * @param FW_HttpRequest
  * @param FW_HttpResponse    
  * Initialisiert response, view und request, so dass diese in jedem Controller
  * verwendet werden können
  * 
  * Die Methode ist final, weil der Konstruktor sehr wichtige Aktionen durchführt
  * Als Konstruktor-Ersatz steht jedem Controller init() zur verfügung
  * init() wird vom Konstruktor ausgeführt
  */ 
 final public function __construct(FW_Http_Request $request, FW_Http_Response $response)
 {
   $this->request = $request;
   $this->response = $response;
   $this->view = FW_View::getView();

   //Ersatz-Konstruktor 
   $this->init();
 }

 /**
  * @access public
  * Dient als Ersatz-Konstruktor
  * Muss nicht von Kindklassen implementiert werden. Deshalb wird er hier als
  * leere Methode vordefiniert    
  */
 public function init() { }

 /**
  * @access protected
  * @param string $method
  * @param array  $parameters  
  *     
  * Wenn eine undefinierte Methode angefordert wird, kommt __call ins spiel
  * Sie wird hier im FW_Controller_Base definiert, weil sowohl FW_Controller als
  * auch FW_SubController diese Methode benötigen.
  * Man kann hier sogenannte ViewDataHelper aufrufen. (z.B. für Title, Meta, 
  * Stylesheets,...)        
  */
  protected function __call($method, $parameters)
  {
    if(!isset($this->ViewDataHelpers[$method]))
    {
      $this->ViewDataHelpers[$method] = new $method();
    }

    return $this->ViewDataHelpers[$method]->run($parameters);

  }
}
?>

Die __call()-Methode ist hier besonders wichtig. Sie legt jeweils eine Instanz von jedem ViewDataHelper (was eigentlich nur ein anderes Wort für ViewHelper ist) an. Dieser wird mit run() ausgeführt.

Ein Beispiel für einen View(Data)Helper

< ?php
/**
 * @licence See: /licence.txt
 */

class FW_View_Helper_Title implements FW_View_Helper_Interface
{
  const REGISTRY_KEY = "___FW__TITLE_TAG__REG";

  private $registry;

  public function run(array $parameters = array())
  {
    $this->registry = FW_Registry::getInstance(self::REGISTRY_KEY);
    return $this;
  }

  public function set($title)
  {
    $this->registry->title = $title;
  }

  public function get($title)
  {
    return (string)$this->registry->title;
  }

  public function __toString()
  {
    return "\n<title>".$this->registry->title."</title>\n";
  }
} 
?>

Man kann jetzt in jedem Controller/SubController den Seitentitel so ändern:

$this->FW_View_Helper_Title()->set("Der neue Titel");

Das sogenannte Fluent Interface wird durch return $this; ermöglicht.

Die Ausgabe des Seitentitels
Oben im Code seht ihr schon eine weitere magische Methode (Interzeptormethode) von PHP: __toString()
Wenn man das Objekt per echo ausgibt, wird __toString() ausgeführt. Sie gibt also einen String zurück.

Dazu habe ich in der Datei /view/Abstract.class.php (Klasse FW_View_Abstract) folgendes geschrieben:

/**
    * @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;
       }

       $helper = new $class;
       return $helper->run($params);
   }

Hier sieht man, dass ViewHelper das gleiche wie ViewDataHelper sind. Der Unterschied ist nur, dass ViewHelper im View aufgerufen werden, ViewDataHelper im Controller.

Noch ein Beispiel für einen View(Data)Helper: Meta-Tags

< ?php
/**
 * @licence See: /licence.txt
 */

class FW_View_Helper_Meta implements FW_View_Helper_Interface
{
  const REGISTRY_KEY = "___FW__META_TAGS__REG";

  private $registry;

  public function run(array $parameters = array())
  {
    $this->registry = FW_Registry::getInstance(self::REGISTRY_KEY);
    return $this;
  }

  public function addMeta($type, $name, $content)
  {
      if($type != "name" && $type != "http-equiv")
      {
        throw new FW_Exception("Invalid Type given!");
      }

      if($type == "name")
      {
        $nameTags = (array)$this->registry->name;
        $nameTags[$name] = $content;
        $this->registry->name = $nameTags;
      }

      if($type == "http-equiv")
      {
        $equiv = (array)$this->registry->equiv;
        $equiv[$name] = $content;
        $this->registry->equiv = $equiv;
      }
  }

  public function addMetaRefresh($time, $target)
  {
    $this->addMeta("http-equiv", "refresh", (int)$time."; ".(string)$target);
  }

  public function addMetaName($name, $content)
  {
    $this->addMeta("name", $name, $content);
  }

  public function addMetaHttpEquiv($name, $content)
  {
    $this->addMeta("http-equiv", $name, $content);
  }

  public function __toString()
  {
    $string = "";

    foreach((array)$this->registry->equiv as $name => $content)
    {
      $string .= "<meta http-equiv=\"".$name."\" content=\"".$content."\" />\n";
    }

    foreach((array)$this->registry->name as $name => $content)
    {
      $string .= "<meta name=\"".$name."\" content=\"".$content."\" />\n";
    }    

    return $string;
  }
} 
?>
1 Star2 Stars3 Stars4 Stars5 Stars (Wurde noch nicht bewertet)
Loading...


Ein Kommentar zu “MVC-Framework: Große Änderung bei Controllers und ViewHelpers”

  1. 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: 31.12.2008
Zeit: 14:17 Uhr
Kategorien: Mein MVC-Framework
Gelesen: 15770x heute: 2x

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

»Meta