Autors Rob Allen, www.akrabat.com
Latviešu valodā tulkojis Ingus Rūķis, www.webtech.lv
Dokumenta versija 1.4.5
Autortiesības © 2006, 2007

Šī pamācība ir paredzēta, lai dotu pavisam vienkāršu ievadu parastas datubāzes aplikācijas izstrādē izmantojot Zend Framework (ZF).

PIEZĪME: Šī pamācība ir pārbaudīta ar Zend Framework versiju 1.0.0. Ir ļoti liela varbūtība, ka viss bez izmaiņām strādās ar jaunākām šī ietvara (framework) versijām, taču nav pārāk liela garantija, ka koda piemēri darbosies ar vecākām ZF versijām.

Model-View-Controller arhitektūra

Pierasts, ka PHP aplikācijas parasti tiek veidotas aptuveni šādā stilā:

<?php
include "common-libs.php";
include "config.php";
mysql_connect($hostname, $username, $password);
mysql_select_db($database);
?>

<?php include "header.php"; ?>
<h1>Home Page</h1>

<?php
$sql = "SELECT * FROM news";
$result = mysql_query($sql);
?>
<table>
<?php
while ($row = mysql_fetch_assoc($result)) {
?>
<tr>
  <td><?php echo $row['date_created']; ?></td>
  <td><?php echo $row['title']; ?></td>
</tr>
<?php
}
?>
</table>
<?php include "footer.php"; ?>

Laikam ejot šāda tipa aplikācijas kļūst arvien grūtāk uzturēt, jo klienti turpina pieprasīt izmaiņas, kuras tiek iepinķerētas kodā dažādās vietās.

Viena no metodēm, kā uzlabot aplikācijas uzturamību ir atdalīt aplikācijas kodu trijās atsevišķās daļās (un parasti arī trijos atsevišķos failos):

Modelis (model) Aplikācijas modeļa daļa ir daļa, kas atbild par parādāmo datu specifiku. Iepriekšējā piemērā tā ir “ziņu” specifika. Tādējādi pamatā modelis atbild par “biznesa” loģikas daļu aplikācijā un vairumā gadījumu nodarbojas ar datu saglabāšanu un nolasīšanu no datubāzēm.
Skats (view) Skata uzdevums ir nodrošināt informācijas attēlošanu lietotājam. Parasti tīmekļa aplikācijās tas ir HTML.
Kontrolieris (controller) Kontrolieris ir aplikācijas daļa, kas sasaista kopā modeļa un skata specifiku, lai nodrošinātu, ka konkrētajā mirklī tiek parādīti pareizie dati.

Zend Framework lieto Model-View-Controller (MVC) arhitektūru. Tā tiek izmantota, lai atdalītu dažādās aplikācijas daļas un padarītu vienkāršāku gan aplikāciju izstrādi, gan arī uzturēšanu.

Prasības

Zend Framework ir šādas prasības:

  • PHP 5.1.4 (vai jaunāka versija)
  • Tīmekļa serveris, kas atbalsta mod_rewrite funkcionalitāti.

Pamācības pieņēmumi

Autors pieņem, ka jūsu rīcībā jau ir Apache tīmekļa serveris ar darbojošos mod_rewrite paplašinājumu un darbojošos PHP versiju 5.1.4 vai jaunāku.

Tāpat jums ir jāpārliecinās, ka Apache ir konfigurēts darbam ar .htaccess failu atbalstu. Tas parasti tiek izdarīts nomainot uzstādījumu:

AllowOverride None

uz

AllowOverride All

jūsu httpd.conf failā. Lai iegūtu vairāk informācijas, meklējiet to atbilstošā dokumentācijā. Gadījumā, ja jūs nebūsiet pareizi nokonfigurējis mod_rewrite paplašinājumu vai .htaccess lietošanu, jūs nevarēsiet pārvietoties uz citām lapām mūsu topošajā piemēra projektā.

Zend Framework iegūšana

Zend Framework var lejupielādēt no http://framework.zend.com/download vai nu .zip vai .tar.gz formātā.

Mapju struktūra

Lai arī Zend Framework nenosaka obligātu mapju struktūru, rokasgrāmata iesaka izvēlēties vienotu mapju struktūru. Šī struktūra pieņem, ka jums ir pilna kontrole pār Apache tīmekļa servera konfigurāciju. Lai padarītu lietas vienkāršākas, mēs to mazliet modificēsim.

Vispirms izveidojiet tīmekļa servera saknes mapē jaunu mapi “zf-tutorial”. Tas nozīmē, ka mūsu veidojamā aplikācija atradīsies atverot šādu adresi: http://localhost/zf-tutorial.

Izveidojiet šādu mapju struktūru, kurā mēs varēsim glabāt aplikācijas failus:

zf-tutorial/
  /application
    /controllers
    /models
    /views
      /filters
      /helpers
      /scripts
  /library
  /public
    /images
    /scripts
    /styles

Kā jūs varat redzēt, mums ir atsevišķas mapes priekš modeļa, skata un kontroliera failiem. Papildus attēli, JavaScript scenāriji un CSS faili tiek glabāti atsevišķās mapēs public mapē. Lejupielādētie Zend Framework faili tiks ievietoti library mapē. Ja mums ir nepieciešamība pēc citām bibliotēkām, tās arī mēs varam ievietot šeit.

Atarhivējiet lejupielādēto Zend Framework arhīva failu. Pārkopējiet mapi library/Zend uz mūsu aplikācijas atbilstošo mapi library. Tagad jūsu zf-tutorial/library mapei vajadzētu saturēt arī mapi Zend.

Sāknēšana (bootstrapping)

Zend Framework kontrolieris Zend_Controller ir projektēts, lai tas atbalstītu tīmekļa lapas ar tīrām adresēm. Lai to nodrošinātu, visiem pieprasījumiem ir jāiet caur vienu index.php failu, kas ir zināms arī kā sāknēšanas fails (bootstrapper). Tas nodrošina mūs ar centrālu vietu visām lapām aplikācijā un ļauj pārliecināties, ka vide ir uzstādīta korekti, lai darbinātu aplikāciju. To mēs nodrošinam ar .htaccess faila palīdzību, kuru jāievieto zf-tutorial saknes mapē:

zf-tutorial/.htaccess

RewriteEngine on
RewriteRule .* index.php
php_flag magic_quotes_gpc off
php_flag register_globals off

RewriteRule ir pavisam vienkāršs nosacījums - “jebkuras adreses vietā lietot index.php”.

Papildus mod_rewrite uzstādījumiem, mēs esam pievienojuši arī dažus PHP uzstādījumus, kas nepieciešami drošībai un tīrībai. Šiem parametriem jau vajadzētu būt uzstādītiem pareizi php.ini failā, taču mēs vēlamies būt pilnīgi droši par pareizu konfigurāciju. Ievērojiet, ka php_flag uzstādījumi .htaccess failos darbojas tikai tad, ja jūs lietojat mod_php. Ja jūs lietojat CGI/FastCGI, tad jums šie uzstādījumi ir jāmaina php.ini fialā.

Lai nu kā, pieprasījumi pēc attēliem, JavaScript un CSS failiem nebūtu jālaiž caur index.php failu. Tā kā šie faili atradīsies public mapē, tad mēs varam Apache norādīt, ka šajā mapē esošie faili ir vienkārši jāatgriež bez jebkādas mod_rewrite iejaukšanās. Vienkāršākais veids, kā to izdarīt, ir izveidot vēlvienu .htaccess failu, šoreiz jau priekš zf-tutorial/public mapes un tajā vienkārši izslēgt mod_rewrite dzinēju:

zf-tutorial/public/.htaccess

RewriteEngine off

Lai arī tas nav pārāk nepieciešams, tomēr mēs varam pievienot vēl dažus mod_rewrite nosacījumus, lai nodrošinātu, ka application un library mapes ir pasargātas:

zf-tutorial/application/.htaccess

deny from all

zf-tutorial/library/.htaccess

deny from all

Ievērojiet, ka lai Apache lietotu .htaccess failus, Apache konfigurācijas failā httpd.conf AllowOverride parametram ir jābūt uzstādītam uz All. Ideja par vairāku .htaccess failu lietošanu ir ņemta no Jayson Minard raksta “Blueprint for PHP Applications: Bootstrapping (Part 2)”. Autors iesaka izlasīt abus rakstus.

Sāknēšanas fails index.php

zf-tutorial/index.php ir mūsu sāknēšanas fails un iesākumā tajā ievietosim šādu kodu:

zf-tutorial/index.php

&lt?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London');

set_include_path('.' . PATH_SEPARATOR . './library'
   . PATH_SEPARATOR . './application/models/'
     . PATH_SEPARATOR . get_include_path());
include "Zend/Loader.php";

Zend_Loader::loadClass('Zend_Controller_Front');

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

// run!
$frontController->dispatch();

Ievērojiet, ka faila beigās netiek liktas PHP koda noslēdzošās iezīmes. PHP interpretators šīs aizverošās iezīmes faila beigās nepieprasa, Zend Framework kodēšanas standartā tiek ieteikts tos arī nelietot, lai izvairītos no grūti atrodamām kļūdām, kuras mēdz radīt lieki simboli aiz noslēdzošajām PHP iezīmēm.

Pamazām iziesim cauri šim failam.

error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London'); 

Šīs rindiņas nodrošina, ka mēs redzēsim savas izdarītās kļūdas (ar nosacījumu, ka jūsu php.ini failā ir uzstādīts konfigurācijas parametrs display_errors ar vērtību on). Tāpat mēs uzstādām arī pašreizējo laika joslu, kā to pieprasa PHP 5.1 un jaunākas versijas. Loģiski, ka šajā vietā jums būtu jāizvēlas sava laika josla, piemēram, ‘Europe/Riga’.

set_include_path('.' . PATH_SEPARATOR . './library'
   . PATH_SEPARATOR . './application/models/'
     . PATH_SEPARATOR . get_include_path());
include "Zend/Loader.php";

Zend Framework ir izstrādāts tā, ka failiem ir jāatrodas iekļaušanas ceļā (include path). Tāpat mēs šajā ceļā pievienojam arī savu modeļu mapi, lai mēs ērti varētu ielādēt modeļu klases. Lai varētu sākt darboties, mums ir jāiekļauj arī Zend/Loader.php, kas mums dod pieeju Zend_Loader klasei, kas satur vajadzīgās funkcijas, ar kuru palīdzību mēs varam ielādēt jebkuru citu Zend Framework klasi.

Zend_Loader::loadClass('Zend_Controller_Front');

Zend_Loader::loadClass ielādē nosaukto klasi. Tas tiek panākts pārveidojot apakšsvītras klases nosaukumā par faila ceļa atdalītājiem un pievienojot galā .php paplašinājumu. Tādējādi Zend_Controller_Front tiks ielādēts no Zend/Controller/Front.php. Ja jūs pieturēsieties pie šādas pašas klašu nosaukumu veidošanas sistēmas, tad varēsiet arī savu klašu ielādei izmantot Zend_Loader::loadClass funkcionalitāti.

Priekšējais kontrolieris (front controller) izmanto maršrutēšanas klasi (router class), lai veiktu sasaisti starp pieprasīto adresi un pareizu PHP funkciju, kas tiks izmantota, lai attēlotu lapu. Lai maršrutētāja klase varētu darboties, tai ir jāsaprot, kurā vietā atrodas mūsu index.php fails, lai visus URI komponentus, kas seko aiz šī index.php tā varētu izmantot kā parametrus. Tas tiek darīts ar Request objekta palīdzību. Tas pietiekami labi spēj automātiski noteikt pareizu bāzes adresi, taču gadījumā, ja tas nedarbojas, jūs varat to pārdefinēt izmantojot šādu funkciju:

function $frontController->setBaseUrl().

Mums ir arī jānokonfigurē priekšējais kontrolieris, lai tas saprastu, kurā mapē meklēt pārējos programmatūras kontrolierus.

$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory('./application/controllers');
$frontController->throwExceptions(true);

Tā kā šī ir pamācība un mēs darbojamies testa sistēmā, autors ir nolēmis padot priekšējam kontrolierim parametru, kas tam liek izvadīt visus kļūdu paziņojumus, kuri notiek. Pēc noklusējuma priekšējais kontrolieris šīs kļūdas noķer un ievieto Response objekta _exceptions parametrā. Response objekts satur visu informāciju par atbildi pieprasītajai lapas adresei. Tas iekļauj HTTP galveni (header), lapas saturu un kļūdas. Priekšējais kontrolieris automātiski nosūtīs galvenes un parādīs lapas saturu tieši pirms darba beigšanas.

Tas varētu likties diezgan mulsinoši cilvēkiem, kas tikai tikko iepazīstas ar Zend Framework. Tādējādi vienkāršāk ir norādīt ZF, lai kļūdas tiktu parādītas uzreiz. Protams, produkcijas vidē lietotājiem nevajadzētu redzēt nekādus neapstrādātus kļūdu paziņojumus!

Visbeidzot mēs esam tikuši līdz mūsu aplikācijas sirdij jeb vietai, kura liek tai darboties:

// run!
$frontController->dispatch();

Ja jūs tagad atvērsiet adresi http://localhost/zf-tutorial/, jums vajadzētu ieraudzīt fatālu kļūdu, kas ir līdzīga šai:

Fatal error: Uncaught exception 'Zend_Controller_Dispatcher_Exception' with
message 'Invalid controller specified (index)' in…

Tas mums norāda, ka mēs vēl neesam pilnībā uzstādījuši savu aplikāciju. Pirms mēs to darām, vispirms noskaidrosim, ko tad īsti mēs būvēsim.

Aplikācija

Mēs būvēsim ļoti vienkāršu uzskaites sistēmu, kas attēlos mūsu CD kolekciju. Pirmā lapa saturēs kolekcijas sarakstu un ļaus mums pievienot, labot un dzēst diskus. Mēs glabāsim mūsu sarakstu datubāzē ar šādu shēmu:

Lauka nosaukums Tips Null? Piezīmes
id Integer No Primary key, Autoincrement
artist Varchar(100) No  
title Varchar(100) No  

Nepieciešamās lapas

Mums būs vajadzīgas šādas lapas.

Mājas lapa Šī lapa attēlos albumu sarakstu un saturēs saites uz albumu labošanu un dzēšanu. Tāpat tā saturēs arī saiti uz albumu pievienošanu.
Jauna albuma pievienošana Šī lapa saturēs formu, kas ļaus pievienot jaunu albumu.
Albuma rediģēšana Šī lapa saturēs formu, kas ļaus labot albumu
Albuma dzēšana Šī lapa pārjautās vai mēs tiešām vēlamies dzēst albumu un izdzēsīs to.

Lapu organizēšana

Pirms mēs sākam uzstādīt savus failus, ir svarīgi saprast, kā Zend Framework ir paredzēts veikt lapu organizēšanu. Katra aplikācijas lapa sava “darbība” (action), bet šīs darbības tiek apvienotas kontrolieros. Piemēram, adresei formā http://localhost/zf-tutorial/news/view kontrolieris ir “news” un darbība ir “view”. Šāda pieeja tiek izmantota, lai ļautu grupēt saistītās darbības. Tā, piemēram, “news” kontrolierim var būt darbības “current”, “archived” un “view”, kas latviskajā variantā varētu atbilst aktuālajām un arhivētajām zinām, kā arī atsevišķa darbība konkrētas ziņas apskatīšanai. Zend Framework MVC sistēma atbalsta arī moduļus, lai varētu grupēt kopā arī kontrolierus, taču mūsu būvējamā sistēma nav pietiekami liela šādam mērķim, tāpēc mēs to nesarežģīsim.

Zend Framework rezervē arī īpašu darbību “index”, kas tiek izmantota kā noklusētā darbība. Tādējādi, ja mums ir adrese http://localhost/zf-tutorial/news/, tad news kontrolierī tiks izpildīta darbība “index”. Zend Framework tāpat rezervē arī noklusēto kontroliera vārdu, ja tāds nav norādīts. Arī te mēs varam iztikt bez lieliem pārsteigumiem, jo arī noklusētā kontroliera nosaukums ir index. Tādējādi adrese http://localhost/zf-tutorial/ liks izpildīties index darbībai index kontrolierī.

Tā kā šī ir vienkārša pamācība, tad šobrīd mēs neuztrauksimies par tādām lietām, kā autorizācija. Līdz tai mēs tiksim kādu citu reizi citā pamācībā.

Tā kā mums ir četras lapas, kas visas ir saistītas ar albumiem, mēs tās apvienosim visas vienā kontrolierī kā četras darbības. Mēs izmantosim noklusēto kontrolieri un mūsu četras darbības būs:

Lapa Kontrolieris Darbība
Mājas lapa Index Index
Jauna albuma pievienošana Index Add
Albuma rediģēšana Index Edit
Albuma dzēšana Index Delete

Jauki un vienkārši!

Kontroliera izveidošana

Tagad mēs esam gatavi izveidot savu kontrolieri. Zend Framework konrolieris ir klase, kas jānosauc kā {Kontroliera nosaukums}Controller. Ievērojiet, ka {Kontroliera nosaukums} ir jāsākas ar lielo burtu. Šai klasei ir jāatrodas atbilstošā {Kontroliera nosaukums}Controller.php failā, kuram savukārt ir jāatrodas mūsu iepriekš norādītajā kontrolieru mapē. Jau atkal {Kontroliera nosaukums} ir jāsākas ar lielo burtu un visiem pārējiem burtiem ir jābūt mazajiem. Katra darbība kontrolierī ir publiska funkcija kontroliera klasē, kura ir jānosauc {darbības nosaukums}Action. Šajā gadījumā {darbības nosaukums} ir jāsākas ar mazo burtu.

Apkopojot iepriekšējā paragrāfā rakstīto, veidosim savu IndexController klasi, kas tiks definēta failā zf-tutorial/application/controllers/IndexController.php:

zf-tutorial/application/controllers/IndexController.php

&lt?php
class IndexController extends Zend_Controller_Action
{
    function indexAction()
    {
    }

    function addAction()
    {
    }

    function editAction()
    {
    }

    function deleteAction()
    {
    }
}  

Mēs esam izveidojuši savas četras darbības, kuras mēs lietosim. Tās nedarbosies, kamēr mēs neizveidosim arī skatus (views).

Katras darbības adrese ir šāda:

Adrese Darbība
http://localhost/zf-tutorial/ IndexController::indexAction()
http://localhost/zf-tutorial/index/add IndexController::addAction()
http://localhost/zf-tutorial/index/edit IndexController::editAction()
http://localhost/zf-tutorial/index/delete IndexController::deleteAction()

Tagad mums ir darbam gatavs aplikācijas maršrutētājs un izveidotas visas četras vajadzīgās aplikācijas darbības.

Ir laiks ķerties klāt skatiem.

Skatu izveidošana

Zend Framework skata komponents arī ir nosaukts ļoti intuitīvi – par Zend_View. Skata komponents ļaums mums atdalīt kodu, kas parāda lapu no koda, kas veic kādas darbības ar datiem.

Pamata Zend_View lietošana ir šāda:

$view = new Zend_View();
$view->setScriptPath('/path/to/view_files');
echo $view->render('view.php');

Var pavisam vienkārši redzēt, ka ja mēs katru reizi katrā darbībā iekļausim šādu strukturālu kodu, tad ātri vien mums apniks katru reizi rakstīt vienu un to pašu koda gabalu. Mēs daudz labprātāk inicializētu skatu kaut kur citur un tad piekļūtu jau inicializētam skata objektam katrā darbības funkcijā.

Zend Framework izstrādātāji jau paredzēja šādu problēmu un tāpēc iebūvēja tās risinājumu vienā no darbību palīgiem (action helper). Zend_Controller_Action_Helper_ViewRenderer parūpējas par view atribūta inicializāciju ($this -> view) un veiks arī paša skata apstrādi (render). Skata apstrādei tiek uzstādīts Zend_View objekts, kas meklēs atbilstošo skatu views/scripts/{kontroliera nosaukums} un arī apstrādās skatu, kura nosaukums sakritīs ar darbības nosaukumu, kam pievienots .phtml paplašinājums. Tas ir, tiks apstrādāts skata scenārijs views/scripts/{kontroliera nosaukums}/{darbības nosaukums}.phtml un apstrādes rezultāts tiks pievienots Response objekta saturam. Response objekts tiek lietots, lai sasaistītu kopā visas HTTP galvenes, saturu un kļūdu paziņojumus, kas radušies lietojot MVC sistēmu. Priekšējais kontrolieris darbu beidzot automātiski nosūta pārlūkprogrammai galvenes un lapas saturu.

Lai pievienotu mūsu aplikācijai skatus, viss, kas mums ir jāizdara, ir jāizveido atbilstošie skatu faili un jāpievieno kontroliera darbībām darbībām kaut kāds saturs, kas tās raksturotu.

Veiksim šādas izmaiņas IndexController kodā (izmaiņas izceltas treknā rakstā):

zf-tutorial/application/controllers/IndexController.php

<?php

class IndexController extends Zend_Controller_Action
{
    function indexAction()
    {
        $this->view->title = "My Albums";
    }

    function addAction()
    {
        $this->view->title = "Add New Album";
    }

    function editAction()
    {
        $this->view->title = "Edit Album";
    }

    function deleteAction()
    {
        $this->view->title = "Delete Album";
    }
}

Katrā funkcijā mēs uzstādām title mainīgo skata atribūtam. Ievērojiet, ka pati lapas parādīšana šeit nenotiek. Tā notiek priekšējā kontrolierī beidzot darbu.

Tagad mums ir jāpievieno aplikācijai četri skata faili. Šie faili ir pazīstami arī kā šabloni un render() metode pieņem, ka katrs šablons tiks nosaukts atbilstošās darbības vārdā ar pievienotu paplašinājumu .phtml, kas norādītu, ka tas ir šablona fails. Failam ir jāatrodas mapē, kas ir nosaukta kontroliera vārdā. Tādējādi mūsu četri faili ir šādi:

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

<html>
<head>
  <title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
  <h1><?php echo $this->escape($this->title); ?></h1>
</body>
</html>

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

<html>
<head>
  <title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
  <h1><?php echo $this->escape($this->title); ?></h1>
</body>
</html>

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

<html>
<head>
  <title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
  <h1><?php echo $this->escape($this->title); ?></h1>
</body>
</html>

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

<html>
<head>
  <title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
  <h1><?php echo $this->escape($this->title); ?></h1>
</body>
</html>

Tagad beidzot var mēģināt darbināt mūsu aplikāciju un staigājot pa iepriekš tabulā norādītajām lapām vajadzētu mainīties tekstiem lapā.

Kopējais HTML kods

Ātri vien var pamanīt, ka šajos šablonos ir pārāk daudz kopīga HTML koda. Mēs izvāksim kopējo HTML kodu atsevišķos failos header.phtml un footer.phtml, kuri atradīsies scripts mapē. Pēc tam mēs tos varam izmantot, lai tie saturētu kopīgi lietojamo HTML kodu un vienkārši atsaukties uz tiem šablonos.

Pēc šīs izmaiņas jaunie faili ir šādi:

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
<div id="content">

Ievērojiet, ka mēs esam arī izlabojuši HTML kodu, tagad tas ir atbilstošs standartiem!

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

&lt/div>
</body>
</html>

Līdz ar šo izmaiņu, ir jāveic arī izmaiņas mūsu skatu failos:

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

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

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('footer.phtml'); ?>

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('footer.phtml'); ?>

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

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

Stili

Lai arī šī ir tikai pamācība, mums būs nepieciešams arī mazliet CSS, lai padarītu mūsu aplikāciju mazliet skaistāku. Te nu ir maza problēmiņa, ka mēs nezinām kā korekti atsaukties uz CSS failu, jo adrese nenorāda uz pareizu saknes mapi. Lai to atrisinātu, mēs varam izmantot getBaseUrl() funkciju, kura ir daļa no Request objekta un varam tās rezultātu padot skatam.

Mēs lietosim IndexController::init() funkciju šim koda gabalam, jo init() tiek izsaukts no konstruktora līdz ar IndexController izveidi un līdz ar to būs pieejams visās darbībās.

zf-tutorial/application/controllers/IndexController.php

...
class IndexController extends Zend_Controller_Action
{
    function init()
    {
        $this->view->baseUrl = $this->_request->getBaseUrl();
    }

    function indexAction()
    {
...

Mums būs jāpievieno CSS fails header.phtml <head> daļā:

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

...
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title><?php echo $this->escape($this->title); ?></title>
  <link rel="stylesheet" type="text/css" media="screen"
             href="<?php echo $this->baseUrl;?>/public/styles/site.css" />
</head>
...

Visbeidzot mums vajadzēs arī pašu CSS kodu:

zf-tutorial/public/styles/site.css

body,html {
  font-size:100%;
  margin: 0;
  font-family: Verdana,Arial,Helvetica,sans-serif;
  color: #000;
  background-color: #fff;
}

h1 {
  font-size:1.4em;
  color: #800000;
  background-color: transparent;
}

#content {
  width: 770px;
  margin: 0 auto;
}

label {
  width: 100px;
  display: block;
  float: left;
}

#formbutton {
  margin-left: 100px;
}

a {
  color: #800000;
}

Tam vajadzētu padarīt mūsu aplikāciju vizuāli pievilcīgāku!

Datubāze

Tagad, kad mēs esam veiksmīgi atdalījuši aplikācijas kontroles līmeni no skata līmeņa, ir laiks pievērsties tās modelim. Atcerieties, ka modelis ir tā aplikācijas daļa, kura veic galvenos aplikācijas uzdevumus ar datiem (tā saukto biznesa loģiku). Mūsu gadījumā šis darbs ar datiem būtībā ir salīdzinoši vienkāršs darbs ar datubāzi. Šim darbam mēs izmantosim Zend Framework ietilpstošo Zend_Db_Table klasi, kuru izmantosim, lai atrastu, saglabātu un dzēstu ierakstus datubāzes tabulā.

Konfigurācija

Lai varētu izmantot Zend_Db_Table, mums ir jānorāda, kuru datubāzi mēs lietosim, kā arī jānorāda pieslēgšanās lietotāja vārds un parole. Tā kā mēs nevēlamies šo informāciju ierakstīt aplikācijas kodā, tad mēs šim mērķim izmantosim konfigurācijas failu.

Zend Framework piedāvā Zend_Config klasi, kas ļauj ar objektorientētu pieeju elastīgi piekļūt konfigurācijas failiem. Konfigurācijas faili var būt gan INI, gan XML faili. Šoreiz mēs lietosim INI failu:

zf-tutorial/application/config.ini

[general]
db.adapter = PDO_MYSQL
db.config.host = localhost
db.config.username = rob
db.config.password = 123456
db.config.dbname = zftest

Saprotams, ka šajā vietā jums ir jānorāda savi datubāzes pieslēgšanās parametri, vai arī datubāzu pārvaldības sistēmā jāuzstāda augstāk redzamie parametri.

Zend_Config lietošana ir pavisam vienkārša:

$config = new Zend_Config_Ini('config.ini', 'section');

Šajā gadījumā ievērojiet, ka Zend_Config_Ini ielādē vienu sadaļu no INI faila, nevis katru sadaļu (tas gan ir iespējams, ja vien ir tāda vajadzība). Klase atbalsta pierakstu ar sekciju nosaukumiem, lati atļautu ielādēt papildus sekcijas. Zend_Config_ini arī uztver punktu parametros kā hierarhisku atdalītāju, lai ļautu grupēt saistītus konfigurācijas parametrus. Tā, piemēram, mūsu config.ini datubāžu servera hosta vārds, lietotāja vārds, parole un datubāzes nosaukums tiks sagrupēti zem $config -> db -> config.

Savu konfigurācijas failu mēs ielādēsim jau sāknēšanas failā (index.php):

Atbilstošā daļa no zf-tutorial/index.php

...
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');

// load configuration
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);

// setup controller
...

Izmaiņas ir atzīmētas ar treknu rakstu. Mēs ielādējam klases, kuras mēs gatavojamies izmantot (Zend_Config_Ini un Zend_Registry) un pēc tam ielādējam “general” sadaļu no application/config.ini faila mūsu $config objektā. Visbeidzot mēs piešķiram $config objektu reģistram, lai tam varētu ērti piekļūt citur aplikācijā.

Piezīme: Šajā pamācībā mums patiesībā nav nepieciešams glabāt $config reģistrā, taču tā ir laba prakse un reālā aplikācijā visticamāk konfigurācijas failā atradīsies ne tikai datubāzes parametri, bet arī vēl citi parametri. Tāpat ievērojiet, ka reģistrs ir mazliet līdzīgs globāliem mainīgajiem un var radīt atkarības starp objektiem, starp kuriem nevajadzētu būt atkarībām. Tāpēc ar to vajadzētu būt prātīgiem.

Zend_Db_Table uzstādīšana

Lai izmantotu Zend_Db_Table, mums ir jānodod datubāzes konfigurācijas informācija, kuru mēs esam tikko ielādējuši. Lai to izdarītu, mums ir jāizveido Zend_Db instance un tad tā jāpiereģistrē ar statisku funkciju Zend_Db_Table::setDefaultAdapter(). To mēs atkal veicam savā sāknēšanas failā index.php. Ar treknu rakstu atkal ir atzīmētas veiktās izmaiņas:

Atbilstošā daļa no zf-tutorial/index.php

...
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');
Zend_Loader::loadClass('Zend_Db');
Zend_Loader::loadClass('Zend_Db_Table');

// load configuration
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);

// setup database
$db = Zend_Db::factory($config->db->adapter,
$config->db->config->toArray());
Zend_Db_Table::setDefaultAdapter($db);

// setup controller
...

Tabulas izveidošana

Šajā pamācībā izmantosim MySQL datubāzu pārvaldības sistēmu, tabulas izveides SQL ir šāds:

CREATE TABLE album (
  id int(11) NOT NULL auto_increment,
  artist varchar(100) NOT NULL,
  title varchar(100) NOT NULL,
  PRIMARY KEY (id)
);

Izpildiet šo teikumu MySQL klientā, piemēram, phpMyAdmin vai standarta MySQL komandrindas klientā.

Testa albumu ievietošana

Tāpat mēs ievietosim arī pāris ierakstus tabulā, lai mēs varētu notestēt datu atlasīšanas funkcionalitāti mājas lapā:

INSERT INTO album (artist, title)
VALUES
('James Morrison', 'Undiscovered'),
('Snow Patrol', 'Eyes Open');

Modelis

Zend_Db_Table ir abstrakta klase, tāpēc mums ir jāatvasina no tās sava klase, kas būs specifiska albumu pārvaldīšanai. Nav svarīgi, kā mēs nosaucam savu klasi, taču nebūtu slikti to nosaukt tādā pašā vārdā, kā tabulu datubāzē. Tāpēc mūsu klases nosaukums būs Album, jo tabulas nosaukums ir album. Lai pateiktu Zend_Db_Table klasei, kuru tabulu tai nāksies apstrādāt, tā ir jānorāda aizsargātajā (protected) $_name parametrā. Tāpat Zend_Db_Table uzskata, ka mūsu tabulai ir lauks ar primāro atslēgu, kura nosaukums ir id. Turklāt tas ir arī auto-increment lauks. Ja vien tas ir nepieciešams, tad šī lauka nosaukums ir maināms, taču vienkāršības labad ir ieteicams pie šādas shēmas pieturēties.

Mēs saglabāsim savu Album klasi modeļu mapē:

zf-tutorial/application/models/Album.php

<?php

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

Neizskatās pārāk sarežģīti, vai ne? Par laimi mūsu prasības bija pietiekoši vienkāršas un Zend_Db_Table iebūvētā funkcionalitāte tās pilnībā apmierina. Gadījumā, ja jums ir nepieciešama specifiska funkcionalitāte modeļa līmenī, tad šī ir tā vieta, kur to rakstīt. Vispārīgā gadījumā papildus metodes, kuras jums būtu jāsaraksta, būtu datu atlases metodes, kas ļautu atlasīt datus pēc jūsu norādītajiem parametriem. Jūs tāpat varat pateikt Zend_Db_Table, lai tā ielasa datus arī no saistītajām tabulām.

Albumu saraksta attēlošana

Tagad, kad mēs esam uzstādījuši konfigurāciju un datubāzes informāciju, mēs varam ķerties vērsim pie ragiem un parādīt mājas lapā kādu albumu no datubāzes. Tas tiek darīts IndexController klasē.

Tā kā katra darbība IndexController klasē darbosies ar album tabulu izmantojot Album klasi, tad ir vērts to ielādēt jau pie IndexController izveides. Tas tiek darīts ar init() funkciju:

zf-tutorial/application/controllers /IndexController.php

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

Ievērojiet, ka šeit ir arī piemērs ar Zend_Loader::loadClass() lietošanu. Tas ielādē mūsu pašu izveidoto Album klasi, jo mēs esam ievietojuši modeļu mapi php iekļaušanas ceļā (include path) index.php failā.

Mēs atlasīsim albumu sarakstu indexAction() funkcijā:

zf-tutorial/application/controllers/IndexController.php

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

Funkcija Zend_Db_Table::fetchAll() atgriež Zend_Db_Table_Rowset, kas ļaus mums iterēt cauri atgrieztajiem ierakstiem skata šablonā:

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> </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'); ?>

Atverot http://localhost/zf-tutorial/ jums vajadzētu ieraudzīt jauku sarakstu ar albumiem (diviem).

Albumu pievienošana

Tagad mēs varam nokodēt albumu pievienošanas funkcionalitāti. Šeit ir divas daļas:

  1. Parādīt formu, kur lietotājam ievadīt informāciju
  2. Apstrādāt formas datus un saglabāt tos datubāzē

To mēs darīsim addAction() funkcijā:

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';
}
...

Ievērojiet, kā mēs pārbaudām Request objekta isPost() metodi, lai uzzinātu, vai forma ir nosūtīta. Ja tā ir nosūtīta, tad mēs nolasām izpildītāja un nosaukuma vērtības no iesūtītā masīva izmantojot Zend_Filter_StripTags klasi, lai nodrošinātos pret html kodu. Tad, pieņemot, ka tie ir ievadīti, mēs izmantojam mūsu Album klasi, lai ievietotu informāciju jaunā rindā tabulā.

Pēc tam, kad esam pievienojuši albumu, mēs pārsūtām lietotāju izmantojot kontroliera _redirect() metodi, lai atgrieztos uz aplikācijas sākumu.

Visbeidzot mēs izveidosim skata formu, kuru lietosim kā šablonu. Aizsteidzoties notikumiem nedaudz priekšā, mēs varam redzēt, ka albuma pievienošanas un rediģēšanas formas būs praktiski identiskas, tāpēc mēs izmantosim kopēju šablona failu (_form.phtml), kurš tiks izsaukts gan no add.phtml, gan edit.phtml:

Šabloni albuma pievienošanai ir:

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>

Šis ir salīdzinoši vienkāršs kods. Tā kā mēs plānojam izmantot _form.phtml arī albuma rediģēšanas operācijām, tad mēs esam izmantojuši $this -> action parametru, nevis vienkārši to ierakstījuši kodā. Līdzīgi mēs izmantojam mainīgo, lai mainītu tekstu uz formas nosūtīšanas pogas.

Albuma rediģēšana

Albuma rediģēšana ir gandrīz identiska pievienošanai, tādējādi kods ir ļoti līdzīgs:

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';
}
...

Ievērojiet, ka tad, ja mēs neesam datu nosūtīšanas režīmā, mēs iegūstam id parametru no Request objekta izmantojot getParam metodi.

Šablons ir šāds:

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'); ?>

Pārstrāde (refactor)!

Cerams, ka jūs pamanījāt, ka addAction() un editAction() ir ļoti līdzīgas metodes un pievienošanas un rediģēšanas šabloni ir identiski. Te nu ir darbs nelielai koda pārstrādāšanai (refactoring).

Tas lai paliek kā treniņa uzdevums jums pašiem.

Albuma dzēšana

Lai nobeigtu mūsu aplikāciju, mums ir jāpievieno albumu dzēšana. Mums ir dzēšanas saite blakus katram albumam albumu saraksta lapā. Vienkāršākais variants būtu izdzēst ierakstu, kad ir uzklikšķināts uz atbilstošās saites. Tas būtu nepareizi. Ja mēs atceramies HTTP specifikāciju, tad izmantojot GET pieprasījumu nedrīkst veikt neatgriezeniskas darbības. Tādos gadījumos ir jālieto POST pieprasījums. Google accelerator beta uz to norādīja daudziem cilvēkiem.

Mēs parādīsim apstiprinājuma formu pēc tam, kad lietotājs būs noklikšķinājis uz dzēšanas saites un tikai tad, ja tālāk tiks nospiests “yes”, ieraksts tiks izdzēsts.

Kods ir savā ziņā līdzīgs pievienošanas un dzēšanas darbībām:

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('/');
}
...

Jau atkal mēs izmantojam to pašu triku ar pieprasījuma metodes pārbaudi, lai noteiktu, vai mums ir jārāda apstiprinājuma forma, vai arī mēs varam veikt ieraksta dzēšanu. Līdzīgi, kā tika darīts ar ieraksta saglabāšanu, arī dzēšana tiek veikta izmantojot Zend_DbTable::delete() metodi.

Ievērojiet, ka mēs atgriežamies uzreiz pēc atbildes satura uzstādīšanas. Tas ir nepieciešams, lai mēs varētu pārsūtīt atpakaļ uz albuma sarakstu funkcijas beigās. Tādējādi, ja kāda no drošības pārbaudēm neizpildās, tad mēs atgriežamies pie albumu saraksta bez _redirect() vairākkārtēja izsaukuma vienas funkcijas ietvaros.

Šablons ir vienkārša forma:

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ēmu novēršana

Ja jums rodas problēmas ar jebkuras citas darbības izpildīšanu izņemot index/index, tad visticamāk, ka maršrutētājs nespēj noteikt, kurā apakšmapē jūsu aplikācija atrodas. Pēc autora līdzšinējiem pētījumiem tas parasti notiek, ja aplikācijas URL atšķiras no mapju ceļa no saknes tīmekļa direktorijas.

Ja noklusētais kods nedarbojas jūsu gadījumā, tad jums ir nepieciešams uzstādīt $baseURL uz pareizu vērtību jūsu serverim:

zf-tutorial/index.php

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

Jums vajadzēs aizvietot ‘mysubdir/zf-tutorial/’ pret pareizu URL ceļu uz index.php. Piemēram, ja jūsu URL uz index.php ir http://localhost/~ralle/zftutorial/index.php, tad pareizā $baseUrl vērtība ir ‘/~ralle/zf-tutorial/’.

Noslēgums

Ar to mēs arī noslēdzam šo pamācību, kā izveidot vienkāršu, taču pilnībā darboties spējīgu MVC aplikāciju, izmantojot Zend Framework. Cerams, ka tā jums likās interesanta un informatīva. Ja tiek atrastas kādas neprecizitātes, tad vēlams rakstīt pamācības autoram rob@akrabat.com angļu valodā.

Šī pamācība apskatīja tikai pašus Zend Framework lietošanas pamatus, vēl ir neskaitāmas citas klases, kuru izmantošanu vajadzētu izpētīt. Būtu ļoti vēlams apmeklēt Zend Framework dokumentāciju (http://framework.zend.com/manual) un iepazīties ar informāciju wiki (http://framework.zend.com/wiki) lai iegūtu padziļinātu informāciju. Ja jūs esat ieinteresēts paša ietvara iztrādē, tad jums būtu vērts aplūkot izstrādātāju wiki (http://framework.zend.com/developer/)

Piebilde no tulkotāja

Gadījumā, ja ir atrastas kādas neprecizitātes tulkojumā, tad būtu vēlams par to paziņot uz ingus@webtech.lv.

Lai varētu labāk izprast Zend Framework darbību, ļoti ieteicama literatūra būtu grāmata “PHP|Architect’s Guide to PHP Design Patterns”. Vismaz man tā ļoti palīdzēja.