Lydia: Separera HTML från kontrollern med vyer

  • Författare
  • Meddelande
Användarvisningsbild

mos

dbwebb

  • Inlägg: 11180
  • Blev medlem: 10 nov 2011, 09:52
  • Ort: Ronneby / Bankeryd

Lydia: Separera HTML från kontrollern med vyer

Inlägg15 feb 2012, 14:12

Detta är en del i tutorialen Lydia: ett PHP-baserat, MVC-inspirerat CMF. Senast uppdaterad 2012-03-22.

Var sak på sin plats, lyft ut HTML-kod från kontrollern och lägg i vyer.


Separera HTML från kontrollern

Tidigare samlade vi all SQL på en plats i koden istället för att beblanda den med PHP-koden (i denna del av tutorialen). Samma sak vill vi uppnå med HTML-koden. Vi vill inte blanda in den i PHP-koden utan vi vill separera ut den. En anledning till detta är att man ofta jobbar i team där någon är bra på frontend-delen av en webbapplikation och jobbar med HTML/CSS medans någon annan är bra på PHP-kodande och då jobbar med kontroller och modeller. Detta behöver inte vara samma person och det underlättar då om man separerar dessa delar. Det blir helt enkelt smidigare att fördela arbetet och det blir framförallt renare kod.

Om vi tittar på gästboken så ser vi att HTML-koden är utspridd över ett par ställen, dels interna variabler och dels i en metod. Mitt mål är nu att separera ut denna koden så att en annan person kan jobba vidare med gästbokens utseende. Jag vill helst lägga allt i en separat fil. Det är detta som kallas för vyer, det är V:et i MVC.

Se hur HTML-koden är inblandad i gästboken.

CCGuestbook
Kod: Markera allt
<?php
class CCGuestbook extends CObject implements IController, IHasSQL {

  private $pageTitle = 'Lydia Guestbook Example';
  private $pageHeader = '<h1>Guestbook Example</h1><p>Showing off how to implement a guestbook in Lydia. Now saving to database.</p>';
  private $pageMessages = '<h2>Current messages</h2>';

  /**
   * Implementing interface IController. All controllers must have an index action.
   */
  public function Index() {   
    $formAction = $this->request->CreateUrl('guestbook/handler');
    $this->pageForm = "
      <form action='{$formAction}' method='post'>
        <p>
          <label>Message: <br/>
          <textarea name='newEntry'></textarea></label>
        </p>
        <p>
          <input type='submit' name='doAdd' value='Add message' />
          <input type='submit' name='doClear' value='Clear all messages' />
          <input type='submit' name='doCreate' value='Create database table' />
        </p>
      </form>
    ";
    $this->data['title'] = $this->pageTitle;
    $this->data['main']  = $this->pageHeader . $this->pageForm . $this->pageMessages;
   
    $entries = $this->ReadAllFromDatabase();
    foreach($entries as $val) {
      $this->data['main'] .= "<div style='background-color:#f6f6f6;border:1px solid #ccc;margin-bottom:1em;padding:1em;'><p>At: {$val['created']}</p><p>" . htmlent($val['entry']) . "</p></div>\n";
    }
  }


Låt oss införa en hantering av vyer i ramverket, det kan hjälpa oss att separera HTML-koden från resten.


En vy för gästboken

Hur kan då en vy se ut för gästboken? Vad är det vi vill uppnå? Du kommer ihåg diskussionerna om template-filer i temat? Det är sådan template-fil vi vill skapa. Det är ett bra arbetsverktyg för frontend-utvecklaren. Genom att extrahera HTML-koden från gästboken så kan vi göra ett utkast på en vy. Så här skulle det kunna se ut.

src/CCGuestbook/index.tpl.php
Kod: Markera allt
<h1>Guestbook Example</h1>
<p>Showing off how to implement a guestbook in Lydia. Now saving to database.</p>

<form action="<?=$formAction?>" method='post'>
  <p>
    <label>Message: <br/>
    <textarea name='newEntry'></textarea></label>
  </p>
  <p>
    <input type='submit' name='doAdd' value='Add message' />
    <input type='submit' name='doClear' value='Clear all messages' />
    <input type='submit' name='doCreate' value='Create database table' />
  </p>
</form>

<h2>Current messages</h2>

<?php foreach($entries as $val):?>
<div style='background-color:#f6f6f6;border:1px solid #ccc;margin-bottom:1em;padding:1em;'>
  <p>At: <?=$val['created']?></p>
  <p><?=htmlent($val['entry'])?></p>
</div>
<?php endforeach;?>


En vy är alltså en del av en webbsida, eller en hel webbsida. Filen ovan är alltså ett exempel på en vy. Som du ser har filen likheter med den template-fil som finns i temat. Det är en mix av HTML med inbäddade PHP-sekvenser.

Hur skall jag då knyta ihop vyerna? När och var skall jag inkludera dem och skriva ut dem? Låt oss kika på ett par alternativ.


Använd output buffering och lagra i en sträng

En variant vi kan använda är att inkludera filen direkt och använda output buffering för att lagra innehållet i en variabel. Detta visar sig bli ett smidigt sätt att få in resultatet i det befintliga temat. Så här blev koden för denna lösningen.

Kod med output buffering
Kod: Markera allt
<?php
class CCGuestbook extends CObject implements IController, IHasSQL {

  private $pageTitle = 'Lydia Guestbook Example';

  /**
   * Implementing interface IController. All controllers must have an index action.
   */
  public function Index() {   
    $this->data['title'] = $this->pageTitle;

    // Include the file and store it in a string using output buffering
    $entries = $this->ReadAllFromDatabase();
    $formAction = $this->request->CreateUrl('guestbook/handler');
    ob_start();
    include __DIR__ . '/index.tpl.php';
    $this->data['main'] = ob_get_clean();
  }


Med funktionerna ob_start() och ob_get_clean() kan jag fånga resultatet som annars skulle skrivits rätt ut till standard output. Därefter kan jag tilldela resultatet till variabeln $this->data['main'] som sedan skrivs ut i temat.


Koppla bort temat och låt kontrollern skapa webbsidan

Detta är ju också ett rimligt alternativ. Då kan kontrollern själv välja vilka vyer som inkluderas och den kan göra det direkt via include() och det som behövs är en möjlighet att stänga av tema hanteringen som sker efter att kontrollern har avslutats. En sådan lösning skulle kunna se ut så här.

Kod med temat bortkopplat och kontrollern skapar sidan
Kod: Markera allt
<?php
class CCGuestbook extends CObject implements IController, IHasSQL {

  private $pageTitle = 'Lydia Guestbook Example';

  /**
   * Implementing interface IController. All controllers must have an index action.
   */
  public function Index() {   
    $this->data['title'] = $this->pageTitle;

    // Disconnect the theme engine and make all output happen in the controller
    unset($this->config['theme']);
    $entries = $this->ReadAllFromDatabase();
    $formAction = $this->request->CreateUrl('guestbook/handler');
    include __DIR__ . '/header.tpl.php';
    include __DIR__ . '/index.tpl.php';
    include __DIR__ . '/footer.tpl.php';
  }


Detta ger kontrollern full kontroll över hur sidorna genereras. Ibland vill man ha det så, kanske tycker man att temat krånglar till det och vill ha en snabb och smidig lösning. Då kan det se ut så här.

Detta var två enkla sätt, låt oss se ett mer avancerat sätt. Kanske ett bättre sätt.


Gör en klass för Vyer

Jag vill här använda temat som jag tänker mig att bygga ut framöver, temat och dess hjälpfunktioner. Jag behöver då en flexibel lösning på att hantera vyer som innehåller fragment av den resulterande webbsidan. Här vet jag inte riktigt hur slutresultatet kommer att se ut så jag behöver något jag kan skapa fort och enkelt men ändå kraftfullt som tillåter att jag bygger ut det när ramverket växer.

Jag väljer en lösning där jag lägger till en klass, CViewContainer, vars syfte är att lagra vyer av olika slag och sedan göra dem tillgängliga för temat's template-filer att skriva ut. Jag gör så att CLydia skapar en instans av klassen och jag låter CObject hantera instansen. Det ger mig en global resurs för vyhanteringen.

Först klassen CViewContainer.

src/CViewContainer/CViewContainer.php
Kod: Markera allt
<?php
/**
* A container to hold a bunch of views.
*
* @package LydiaCore
*/
class CViewContainer {

   /**
    * Members
    */
   private $data = array();
   private $views = array();
   

   /**
    * Constructor
    */
   public function __construct() { ; }


   /**
    * Getters.
    */
  public function GetData() { return $this->data; }
 
 
   /**
    * Set the title of the page.
    *
    * @param $value string to be set as title.
    */
   public function SetTitle($value) {
     $this->SetVariable('title', $value);
  }


   /**
    * Set any variable that should be available for the theme engine.
    *
    * @param $value string to be set as title.
    */
   public function SetVariable($key, $value) {
     $this->data[$key] = $value;
  }


   /**
    * Add a view as file to be included and optional variables.
    *
    * @param $file string path to the file to be included.
    * @param vars array containing the variables that should be avilable for the included file.
    */
   public function AddInclude($file, $variables=array()) {
     $this->views[] = array('type' => 'include', 'file' => $file, 'variables' => $variables);
  }


   /**
    * Render all views according to their type.
    */
   public function Render() {
     foreach($this->views as $view) {
      switch($view['type']) {
        case 'include':
          extract($view['variables']);
          include($view['file']);
          break;
      }
     }
  }

}


Det är egentligen inte mycket kod i klassen, tanken är att den skall vara en kontainer och lagra vyer och variabler. Det är lite som $ly->data men nu med hantering av vyer också. Blir det här bra så innebär det att vi kan låta $ly->data övergå till $ly->views istället.

Klassen initieras i konstruktorn i CLydia.
Kod: Markera allt
     // Create a container for all views and theme data
     $this->views = new CViewContainer();


Gästboken kan vi nu uppdatera och koden som gör all magi ser nu ut så här.

CCGuestbook::Index()
Kod: Markera allt
  /**
   * Implementing interface IController. All controllers must have an index action.
   */
  public function Index() {
    $this->views->SetTitle($this->pageTitle);
    $this->views->AddInclude(__DIR__ . '/index.tpl.php', array(
      'entries'=>$this->ReadAllFromDatabase(),
      'formAction'=>$this->request->CreateUrl('guestbook/handler')
    ));
  }


Två rader av kod, en sätter sidans titel som en variabel i $ly->views och den andra raden lägger till vyn i kontainern. Den processas inte förrän i tema hanteringen. Det som läggs till är filen som är vyns template-file och en array med de variabler som skall exponeras mot filen när den inkluderas.

All HTML-kod är nu borta från kontrollern. Den text som är kvar är sidans titel. Det kan vi leva med.

I temahanteringen så används nu variablerna från CViewContainer tillsammans med den gamla $ly->data. Det sker i slutet av CLydia::ThemeEngineRender().

CLydia::ThemeEngineRender()
Kod: Markera allt
   /**
    * ThemeEngineRender, renders the reply of the request to HTML or whatever.
    */
  public function ThemeEngineRender() {
    // code removed by purpose...

    // Extract $ly->data and $ly->view->data to own variables and handover to the template file
    extract($this->data);     
    extract($this->views->GetData());     
    include("{$themePath}/default.tpl.php");
  }


Det som återstår är att temat's template-file skall kunna anropa en funktion som renderar alla vyer. Det löser jag med en hjälp-funktion till temat som läggs i theme/functions.php.

theme/functions.php: render_views()
Kod: Markera allt
/**
* Render all views.
*/
function render_views() {
  return CLydia::Instance()->views->Render();
}


I template-filen default.tpl.php anropas hjälp-funktionen.

(del av) default.tpl.php
Kod: Markera allt
  <div id='wrap-main'>
    <div id='main' role='main'>
      <?=@$main?>
      <?=render_views()?>
    </div>
  </div>


Så där, det var allt. Nu återstår bara att fundera vilken lösning du tycker är bäst, eller kanske du har en bättre variant?


Summering & Test

Här fick vi en genomgång om vad vyer är och olika sätt som vi kan implementera dem i vårt ramverk. Jag valde att göra den mer avancerade delen, det känns bäst med tanke på utbyggnaden av mitt ramverk. Men vill man ha snabba lösningar så kan man finna dem också, utan att få dåligt samvete. Den stora vinsten i detta är nu att kontrollern för gästboken är ren från HTML-kod och det var målet med denna övningen. Det blir nu lättare att ta in ytterligare personer i teamet och fördela arbetet. Just denna möjlighet att fördela arbetet är en av fördelarna med tänket bakom MVC-ramverk.

Koden kan du testköra och studera på nedanstående länkar. Resultatet ser likadant ut som tidigare, vi har bara strukturerat om vår kod.

http://dbwebb.se/lydia/tags/v0.1.7/guestbook
https://github.com/mosbth/lydia/tree/v0.1.7

Fortsätt med nästa tutorial.
...
..:
.... /mos

Vilka är online

Användare som besöker denna kategori: Google [Bot] och 31 gäster