Začíname so Zend Frameworkom – tutoriál [3/3]

23.12.2007 14:19

V poslednom treťom diely tutoriálu aplikáciu dokončíme. Pozrieme sa detailne na prácu jednotlivých komponentov MVC systému.

<prelozeny-text>
Model
Zend_Db_Table je abstraktná trieda, takže musíme odvodiť vlastnú triedu, takú aby vyhovovala našej štruktúre s albumami. Nezáleží ako nazveme našu triedu, ale zvyklosťou je nazývať ju rovnako ako databázovú tabuľku. Takže naša trieda sa bude volať Album rovnako ako naša databázová tabuľka. Aby sme triede Zend_Db_Table povedali meno tabuľky, s ktorou bude pracovať, musíme nastaviť chránenú premennú $_name podľa mena tabuľky. Okrem toho trieda Zend_Db_Table predpokladá, že má primárny kľúč nazvaný id a má nastavený príznak autoincrement. Meno tohto poľa môže byť samozrejme zmenené podľa želania.

Našu triedu Album uložíme do adresára s modelmi:

zf-tutorial/application/models/Album.php

<?php
  class Album extends Zend_Db_Table
  {
    protected $_name = 'album';
  }

Nie veľmi zložité, že áno. Našťastie naše požiadavky sú veľmi jednoduché a trieda Zend_Db_Table poskytuje všetku funkcionalitu, ktorú budeme potrebovať. Ale ak budete potrebovať nejakú špecifickú funkcionalitu od svojho modelu, toto je to miesto kde zapíšete požadovaný kód. Vo všeobecnosti sa najviac pridávajú metódy na vyhľadávanie, také ktoré vyberajú z databázy údaje, ktoré potrebujete/hľadáte. Môžete tiež Zend_Db_Table povedať o súvisiacich tabuľkách a nechať ju aby vybrala aj tieto súvisiace údaje.

Vylistovanie albumov
Teraz už máme nastavenú konfiguráciu a vytvorené potrebné databázové údaje, môžeme sa zakúsiť do mäsa aplikácie a zobraziť nejaké albumy. Toto sa vykonáva v triede IndexController.

V skutočnosti, každá akcia v rámci IndexController bude manipulovať s tabuľkou album vďaka využívaniu metód z triedy Album. Takže je vhodné načítať triedu Album v momente volania controllera. Toto spravíme v jeho init() funkcii:

zf-tutorial/application/controllers /IndexController.php

...
function init()
{
  $this->view->baseUrl = $this->_request->getBaseUrl();
  Zend_Loader::loadClass('Album');
}
...

Pozn.: Toto je príklad použitia Zend_Loader::loadClass() na načítanie naších vlastných tried, vďaka tomu, že sme náš adresár s modelmi pridali do inkludovacej cesty v súbore index.php.

Teraz si vylistujeme naše albumy z tabuľky v indexAction():

zf-tutorial/application/controllers/IndexController.php

...
function indexAction()
{
  $this->view->title = "My Albums";
  $album = new Album();
  $this->view->albums = $album->fetchAll();
}
...

Funkcia Zend_Db_Table::fetchAll() vráti Zend_Db_Table_Rowset, ktorý nám umožňuje iterovať vo vrátených riadkoch tabuľky v našej šablóne view:

zf-tutorial/application/views/scripts/index/index.phtml

<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<p><a href="<?php echo $this->baseUrl; ?>/index/add">Add new album</a></p>
<table>
<tr>
  <th>Title</th>
  <th>Artist</th>
  <th>&nbsp;</th>
</tr>
<?php foreach($this->albums as $album) : ?>
<tr>
  <td><?php echo $this->escape($album->title); ?></td>
  <td><?php echo $this->escape($album->artist); ?></td>
  <td>
    <a href="<?php echo $this->baseUrl; ?>/index/edit/id/<?php
                echo $album->id; ?>">Edit</a>
    <a href="<?php echo $this->baseUrl; ?>/index/delete/id/<?php
                echo $album->id; ?>">Delete</a>
  </td>
</tr>
<?php endforeach; ?>
</table>
<?php echo $this->render('footer.phtml'); ?>

Po zadaní adresy http://localhost/zf-tutorial/ (alebo inej s odpovedajúcou činnosťou) do browsera, sa ukáže pekná tabuľka s (dvoma) albumami.

Pridanie nových albumov
Teraz môžeme napísať kód, ktorý zabezpečuje pridávanie albumov. Tento proces je možné rozdeliť do dvoch častí:

  • Zobrazenie formulára, aby užívateľ mohol zadať vstupné údaje
  • Spracovanie údajov od užívateľa a ich zapísanie do databázy

Tieto funkcie sa vykonávajú v addAction():

zf-tutorial/application/controllers/IndexController.php

...
function addAction()
{
  $this->view->title = "Add New Album";
  if ($this->_request->isPost()) {
    Zend_Loader::loadClass('Zend_Filter_StripTags');
    $filter = new Zend_Filter_StripTags();
    $artist = $filter->filter($this->_request->getPost('artist'));
    $artist = trim($artist);
    $title = trim($filter->filter($this->_request->getPost('title')));
    if ($artist != '' && $title != '') {
      $data = array(
        'artist' => $artist,
        'title' => $title,
      );
      $album = new Album();
      $album->insert($data);
      $this->_redirect('/');
      return;
    }
  }
  // set up an "empty" album
  $album = new Album();
  $this->view->album = $album->createRow();
  // additional view fields required by form
  $this->view->action = 'add';
  $this->view->buttonText = 'Add';
}
...

Všimnite si ako kontrolujeme pomocou metódy isPost() z request objektu, to či sme obdržali nejaké údaje od užívateľa. Ak áno, vyberieme si údaj o názve umelca a jeho albumu (polia artist a title) z poľa POST za pomoci triedy Zend_Filter_StripTags , tak aby sme zabezpečili zakázanie ukladania HTML tagov. Ďalej otestujeme, či sú tieto údaje vyplnené a potom opäť zapojíme do práce naš model a jeho triedu Album, k tomu aby sme údaje zapísali do nového riadku v databázovej tabuľke.

Potom ako pridáme nový album, použijeme metódu _redirect() controllera na to aby sme sa vrátili späť na titulnú stránku aplikácie.

Nakoniec ešte pripravíme view, ktorý bude načítavať príslušnú šablónu s formulárom. Ak trocha predbehneme, uvidíme, že akcia edit je veľmi podobná tejto, takže použijeme spoločný súbor ako šablónu formulára (_form.phtml), ktorá sa bude načítavať z oboch akčných šablón add.phtml a edit.phtml. Šablóny na pridanie albumu sú:

zf-tutorial/application/views/scripts/index/add.phtml

<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php echo $this->render('index/_form.phtml'); ?>
<?php echo $this->render('footer.phtml'); ?>

zf-tutorial/application/views/scripts/index/_form.phtml

<form action="<?php echo $this->baseUrl ?>/index/<?php
  echo $this->action; ?>" method="post">
<div>
  <label for="artist">Artist</label>
  <input type="text" name="artist"
    value="<?php echo $this->escape(trim($this->album->artist));?>"/>
</div>
<div>
  <label for="title">Title</label>
  <input type="text" name="title"
    value="<?php echo $this->escape($this->album->title);?>"/>
</div>
<div id="formbutton">
  <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" />
  <input type="submit" name="add"
    value="<?php echo $this->escape($this->buttonText); ?>" />
</div>
</form>

Editácia albumu
Editácia albumu je takmer identická činnosť ako pridanie albumu, takže kód je veľmi podobný:

zf-tutorial/application/controllers/IndexController.php

...
function editAction()
{
  $this->view->title = "Edit Album";
  $album = new Album();
  if ($this->_request->isPost()) {
    Zend_Loader::loadClass('Zend_Filter_StripTags');
    $filter = new Zend_Filter_StripTags();
    $id = (int)$this->_request->getPost('id');
    $artist = $filter->filter($this->_request->getPost('artist'));
    $artist = trim($artist);
    $title = trim($filter->filter($this->_request->getPost('title')));
    if ($id !== false) {
      if ($artist != '' && $title != '') {
        $data = array(
          'artist' => $artist,
          'title' => $title,
        );
        $where = 'id = ' . $id;
        $album->update($data, $where);
        $this->_redirect('/');
        return;
      } else {
        $this->view->album = $album->fetchRow('id='.$id);
      }
    }
  } else {
    // album id should be $params['id']
    $id = (int)$this->_request->getParam('id', 0);
    if ($id > 0) {
      $this->view->album = $album->fetchRow('id='.$id);
    }
  }
  // additional view fields required by form
  $this->view->action = 'edit';
  $this->view->buttonText = 'Update';
}
...

Všimnite si, že nie sme v „POST“ móde, údaje získavame z parametra id z člena params objektu request, použitím metódy getParam().

Šablóna má tento kód:

zf-tutorial/application/views/scripts/index/edit.phtml

<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php echo $this->render('index/_form.phtml'); ?>
<?php echo $this->render('footer.phtml'); ?>

Refaktorácia
Nemali by ste nechať nepovšimnuté, že akcie addAction() a editAction() sú veľmi podobné a že šablóny pre pridanie a editovanie albumov sú identické. Mali by ste istý čas venovať refaktorácii tohto kódu. Túto úlohu už nechávam na Vás, čitateľoch.

Zmazanie albumu
Na dokončenie našej aplikácie, potrebujeme ešte implementovať mazanie albumov. Už máme pripravené linky vedľa každého vylistovaného albumu a prirodzenou činnosťou, ktorú by mali vykonávať, je zmazanie albumu po kliknutí na odkaz. Toto by bolo ale nesprávne. Jedna z poučiek pri špecifikácii HTTP, pripomína, že nevratná (irreversible) činnosť by mala používať techniku POST namiesto GET.

Po kliknutí na odkaz pre zmazanie albumu zobrazíme potvrdzovací dialóg a ak užívateľ klikne na tlačidlo „Áno“, zmažeme záznam.

Kód je trocha podobný ako pri akciách add() a edit():

zf-tutorial/application/controllers/IndexController.php

...
function deleteAction()
{
  $this->view->title = "Delete Album";
  $album = new Album();
  if ($this->_request->isPost()) {
    Zend_Loader::loadClass('Zend_Filter_Alpha');
    $filter = new Zend_Filter_Alpha();
    $id = (int)$this->_request->getPost('id');
    $del = $filter->filter($this->_request->getPost('del'));
    if ($del == 'Yes' && $id > 0) {
      $where = 'id = ' . $id;
      $rows_affected = $album->delete($where);
    }
  } else {
    $id = (int)$this->_request->getParam('id');
    if ($id > 0) {
      // only render if we have an id and can find the album.
      $this->view->album = $album->fetchRow('id='.$id);
      if ($this->view->album->id > 0) {
        // render template automatically
        return;
      }
    }
  }
  // redirect back to the album list unless we have rendered the view
  $this->_redirect('/');
}
...

Opäť používame rovnaký trik na overenie metódy požiadavky, na to aby sme vedeli či zobraziť potvrdzovací formulár alebo či máme vykonať zmazanie s pomocou triedy Album(). Podobne ako insert a update, aktuálne zmazanie sa vykonáva pomocou volania Zend_Db_Table::delete().

Všimnite si, že sa vraciame na titulku hneď po vytvorení tela odpovede. Takto sa môžeme vrátiť k listingu albumov, hneď po dokončení funkcie. Ak niektorá z ošetrovacích funkcií zlyhá, vrátime sa na titulku bez potreby volať _redirect() niekoľkokrát v rámci akcie.

Šablóna pre zmazanie má je jednoduchý formulár:

zf-tutorial/application/views/scripts/index/delete.phtml

<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php if ($this->album) :?>
<form action="<?php echo $this->baseUrl ?>/index/delete" method="post">
<p>Are you sure that you want to delete
  '<?php echo $this->escape($this->album->title); ?>' by
  '<?php echo $this->escape($this->album->artist); ?>'?
</p>
<div>
  <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" />
  <input type="submit" name="del" value="Yes" />
  <input type="submit" name="del" value="No" />
</div>
</form>
<?php else: ?>
<p>Cannot find album.</p>
<?php endif;?>
<?php echo $this->render('footer.phtml'); ?>

Problémy
Ak máte problémy prinútiť k funkcii ktorúkoľvek akciu okrem index/index, tak dôvodom je najpravdepodobnejšie neschopnosť routra odvodiť podadresár, kde je uložená aplikácia. Podľa môjho malého prieskumu sa to deje ak sa URL k vášmu webu odlišuje od cesty k web-root. Ak Vám teda nefunguje tento nemodifikovaný kód, mali by ste premennej $baseURL nastaviť správny obsah podľa Vášho servera:

zf-tutorial/index.php

...
// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setBaseUrl('/mysubdir/zf-tutorial');
$frontController->setControllerDirectory('./application/controllers');
...

Nahraďte reťazec '/mysubdir/zf-tutorial/' správnou cestou k index.php. Napr. ak je vaša cesta k index.php http://localhost/~ralle/zf-tutorial/index.php, správna hodnota by mala byť '/~ralle/zf-tutorial/'.

Záver
Vytvorili sme si plne funkčnú MVC aplikáciu s použítím Zend Frameworku. Dúfam, že bol tento tutoriál pre vás zaujímavý a prínosný. Ak nájdete akúkoľvek chybu, prosím pošlite mi email na rob AT akrabat DOT com!

Tento tutoriál sa pozrel iba na základné použitie frameworku. Pred vami je ešte veľa tried, ktoré čakajú na vaše preskúmanie! Dobrým začiatkom je prečítanie manuálu, alebo návšteva wiki stránok. Ak máte záujem prispievať k vývoju frameworku, navštívte toto miesto.
</prelozeny-text>

Tak drahá komunita, dúfam že sa vám môj darček páčil a použijete ho k svojmu prospechu a osobnému rastu. Rob Allen napísal aj pokračovanie (rozšírenie) tutoriálu, kde popisuje použitie triedy Zend_Auth, pomocou ktorej je možné aplikáciu rozšíriť o funkcie login/logout. Toto rožšírenie tutoriálu sa chystám tiež preložiť a zverejniť zase na BH. Takže sa je načo tešiť.

Nakoniec ešte pridám zopár relevantných odkazov, kde nájdete ďalšie informácie o Zend Frameworku alebo objektovom programovaní v PHP:

Ronnie weblog
ZF Tutorials
Naneau Tutorials
Knihovna PHP
Seriál PHP v objetí objektů

Štastné a veselé prežitie vianočných sviatkov a nového roku praje Srigi.