Descriem operaţiile de bază pentru a face o aplicaţie Web folosind Zend Framework. Pentru început ne propunem o aplicaţie CRUD pentru un tabel MySQL simplu (vezi totuşi ian 2010).

În cazul unui sistem Windows - vezi XAMPP (şi eventual… articolele noastre anterioare). Aici vom presupune un sistem Linux (Debian GNU/Linux), având instalat un server Apache/2 şi PHP/5.2.

De ce am folosi un framework?!

În Aplicaţie PHP pentru operaţii CRUD pe tabelele unei baze de date am realizat o aplicaţie Web pentru a asigura operaţii CRUD (listare, updatare, inserare, ştergere) asupra oricărui tabel (independent de numele câmpurilor acestuia) ales "run-time" dintr-o bază de date precizată.

De data aceasta ne propunem (la început) o aplicaţie mult mai modestă: operaţii CRUD pe un anumit tabel precizat (angajând direct numele câmpurilor) - în schimb, vom angaja pentru aceasta un framework; ne-am aştepta atunci, ca efortul de programare să fie mai mic - dar nu este aşa (cel puţin pentru primele încercări; dar să ne înţelegem: "programare" nu înseamnă doar scrierea unor mai multe sau mai puţine linii de cod…).

Şi atunci, întrebarea "De ce să folosim" pare firească… dar firesc este şi răspunsul: pentru a vedea avantajele folosirii unui framework trebuie abordată o aplicaţie suficient de complexă (de exemplu, angajând mai multe tabele relaţionate, implicând validarea formularelor, autentificare şi autorizare, etc.) şi nicidecum vreuna banală, precum cea învederată aici.

Să observăm până una-alta, că ideea de "framework" este… veche. Păi iată o întrebare similară celeia puse mai sus: de ce să folosim algebra când de fapt putem rezolva şi doar folosind cunoştinţele elementare? Cică:

Nişte găini şi nişte iepuri au împreună 50 de capete şi 140 de picioare. Câte găini şi câţi iepuri sunt?

O soluţia elementară necesită ingeniozitate: să ne imaginăm că fiecare găină stă într-un singur picior, iar fiecare iepure stă pe labele din spate; se folosesc astfel jumătate din picioare, deci 140 : 2 = 70 de picioare. Însă în acest număr 70 - fiecare cap de găină este socotit o dată, iar fiecare cap de iepure de două ori; fiindcă sunt în total 50 de capete, diferenţa 70 - 50 = 20 reprezintă numărul de iepuri. Răspuns: 20 iepuri şi 30 găini.

Nu-i cazul să redăm aici şi soluţia algebrică. Dar am putea observa că "limbajul algebric" constituie un framework veritabil, permiţând abordarea unitară a oricărei probleme de genul celei evocate: trebuie instituite variabilele necesare (fie x = numărul de iepuri, etc.), trebuie exprimate relaţiile corespunzătoare (x + y = 50, etc.) şi în final trebuie rezolvate nişte ecuaţii.

Această schemă - reducerea la o problemă de algebră - pe lângă faptul că este foarte convenabilă (scutindu-ne de raţionamente ingenioase), poate părea azi extrem de simplă; dar să ne amintim că la început, prin clasa a IV-a sau a V-a - ne-om fi chinuit destul toţi, ca să o deprindem!

Cam la fel stau lucrurile şi acum, în privinţa deprinderii lucrului cu Zend Framework (ZF, mai departe); pentru a "prinde", trebuie să începi cu aplicaţii banale. Dar încercăm totuşi, să nu ocolim conexiunile şi contextele în care se produc lucrurile şi să vizăm "printre rânduri", posibile aplicaţii mai complexe.

Instalări şi configurări preliminare

Alegem să instalăm ZF de la Debian, folosind apt-get ("Advanced Package Tool" - operează (update, remove, check, etc.) ţinând cont de toate dependenţele faţă de alte pachete):

     sudo apt_get install zendframework*

Fişierele care compun framework-ul (grupate în subdirectoarele specifice) sunt instalate în /usr/share/php/Zend; fiindcă opţiunea de configurare PHP include_path din fişierul /etc/php5/apache2/php.ini deja conţine calea :/usr/share/php, rezultă că şi fişierele din php/Zend vor fi accesibile dintr-o aplicaţie sau alta. Să observăm că astfel, mai multe aplicaţii independente vor putea folosi aceeaşi instalare ZF; alternativa (şi aceasta este aproape obligatorie în cazul unor alte framework-uri) era de a instala ZF în cadrul fiecărei aplicaţii.

Comanda redată mai sus instalează şi pachetul "zendframework-bin", rezultând fişierul /usr/share/zendframework/bin/zf.php precum şi scriptul /usr/bin/zf (pe Windows: zf.bat); scriptul zf poate fi invocat dintr-un terminal şi va lansa programul utilitar zf.php (presupunând că este activată interfaţa CLI ("Command Line Interface") pentru PHP, ceea ce este implicit pentru PHP5).

zf.php permite crearea automată (din linia de comandă) a scheletului aplicaţiei; de exemplu zf create project MyApp va crea structura de directoare tipică pentru o aplicaţie care foloseşte ZF, împreună cu principalele fişiere (având iniţial, conţinutul minim necesar).

Ca şi alte framework-uri, ZF prevede posibilitatea de a folosi fişiere Htaccess. Fişierele .htaccess (dacă există în directorul public respectiv) sunt citite de fiecare dată când se deserveşte o cerere HTTP, având astfel efect imediat (nu este necesară restartarea serverului Apache, ca în cazul când configurările respective s-ar face în fişierul global de configurare a serverului). Desigur, implicarea de fişiere .htaccess este necesară în cazul când nu este permis accesul la fişierele de configurare de pe server, dar reduce oarecum performanţa (fiindcă încarcă rezolvarea fiecărei cereri cu o serie de operaţii suplimentare).

Pentru a suporta fişiere .htaccess este necesară extensia Apache mod_rewrite şi presupunem că acest modul este instalat: există /usr/lib/apache2/modules/mod_rewrite.so, iar /etc/apache2/mods-available conţine şi fişierul rewrite.load. Pentru ca modulul respectiv să fie şi activat (nu doar "instalat"), mai trebuie să existe un symlink pentru "rewrite.load" în /etc/apache2/mods-enabled; dacă nu există, atunci putem crea symlink-ul respectiv folosind programul a2enmod (prin sudo a2enmod rewrite).

Să observăm că acelaşi mecanism de instituire se foloseşte (în Linux/Debian) şi pentru a crea un VirtualHost, implicând /etc/apache2/sites-available, respectiv sites-enabled şi programul a2ensite. Presupunem că am creat deja (a vedea iarăşi ian 2010) un VirtualHost pentru aplicaţia anunţată:

<VirtualHost *:80>
    DocumentRoot /home/vb/zendApp1/public
    ServerName zendapp1.d
    <Directory /home/vb/zendApp1/public>
        DirectoryIndex index.php
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

În baza acestei definiţii, aplicaţia va fi accesată prin http://zendapp1.d/. Apache va rezolva această cerere punând în execuţie fişierul index.php din directorul /home/vb/zendApp1/public; dar înainte de a rezolva astfel, va "vedea" - respectând directiva AllowOverride All - dacă în directorul respectiv există un fişier .htaccess, caz în care întâi va executa directivele citite din acest fişier (dacă AllowOverride ar fi rămas "None" în loc de "All", atunci Apache ar ignora fişierele .htaccess).

Un ultim amănunt: înainte de a invoca a2ensite (pentru a încheia operaţie de constituire a virtualhost-ului nostru), trebuie creat /home/vb/zendApp1/public - aşa că este mai potrivit ca întâi să folosim zf.php (creând automat structura de directoare necesară) şi abia după aceea, a2ensite.

Structura aplicaţiei şi deducerea funcţionării

Programul zf.php (împreună cu scriptul de shell zf, sau zf.bat în cazul unui sistem Windows) este o interfaţă pentru linia de comandă, a componentei Zend_Tool din ZF. Comanda următoare creează structura de bază (directoare, subdirectoare, fişiere) necesară aplicaţiei noastre:

     vb@localhost:~$  zf  create  project  zendApp1

După executarea acestei comenzi (urmată de invocarea programului a2ensite, ca să definitivăm constituirea virtualhost-ului), deja putem accesa "aplicaţia": http://zendapp1.d, obţinând în browser o binemeritată pagină de "Welcome" (ceea ce desigur, va încânta pe utilizatorul ZF).

Putem vizualiza conţinutul folderului creat folosind programul tree (sudo apt-get install tree).

Ştiind definiţia virtualhost-ului aplicaţiei, conţinutul directorului acesteia şi pe de altă parte, vizualizând sursa generată de invocarea ei dintr-un browser (redate în imaginea de mai sus) - putem deduce cum funcţionează aplicaţia şi implicit, care sunt principiile de bază ale framework-ului ZF (angajând desigur, o anumită experienţă de investigare şi de programare).

Din definiţia virtualhost-ului corespunzător, ştim că singurul director accesibil cererilor HTTP de pe Web este subdirectorul /public; vedem că acesta conţine numai fişierele .htaccess şi index.php. Definiţia virtualhost-ului stabileşte (prin directiva DirectoryIndex) că o cerere http://zendapp1.d/ va fi satisfăcută punând în execuţie fişierul index.php (dar aceasta, după ce în prealabil - datorită directivei AllowOverride All - se execută specificaţiile din .htaccess, pe care deocamdată le putem ignora).

Prin urmare, pagina de "Welcome" (redată parţial în imaginea de mai sus) este rezultatul execuţiei fişierului index.php. Deschidem (într-un editor de text) acest fişier, dar pentru început ne mulţumim să constatăm că el nu produce (direct) pagina "Welcome"; înseamnă că această pagină este constituită într-un fişier dinafara directorului public - fişier probabil invocat sau inclus printr-un anumit mecanism, din index.php.

Explorând arborele corespunzător folderului zendApp1/ (vezi imaginea de mai sus), vedem index.phtml pe calea application/views/scripts/index/ şi (deschizându-l într-un editor de text) constatăm că acesta corespunde paginii "Welcome".

index.phtml arată (la nivel de sursă) ca un fişier HTML obişnuit şi dacă îi schimbăm extensia (eliminând p din .phtml) atunci el poate fi deschis direct din browser (prin file:///home/vb/zendApp1/application/views/scripts/index/index.html), obţinând exact pagina "Welcome" amintită. Însă dacă - după această schimbare - relansăm http://zendapp1.d/, atunci obţinem o "pagină de eroare", din care copiem aici:

An error occurred
Application error
Exception information:

Message: script 'index/index.phtml' not found in path (/home/vb/zendApp1/application/views/scripts/)
Stack trace:

#0 /usr/share/php/Zend/View/Abstract.php(876): Zend_View_Abstract->_script('index/index.pht...')
etc.

Putem identifica uşor sursa acestei pagini de eroare: application/views/scripts/error/error.phtml; de data aceasta este vorba de un fişier HTML care conţine şi nişte instrucţiuni PHP:

<body>
  <h1>An error occurred</h1>
  <h2><?php echo $this->message ?></h2>

  <?php if (isset($this->exception)): ?>

  <h3>Exception information:</h3>
  <p>
      <b>Message:</b> <?php echo $this->exception->getMessage() ?>
  </p>

  <h3>Stack trace:</h3>
  <pre><?php echo $this->exception->getTraceAsString() ?>
  </pre>

  <h3>Request Parameters:</h3>
  <pre><?php echo var_export($this->request->getParams(), true) ?>
  </pre>
  <?php endif ?>

</body>

Provocarea excepţiilor este o strategie foarte eficientă pentru a clarifica lucrurile ("din greşeli se învaţă!"). Inspiraţia de a obţine o pagină de eroare (nedorită, altfel) şi faptul că ZF informează suficient de edificator despre erori — ne creează acum şansa de a clarifica lucrurile prin scrutarea mesajelor obţinute (corelând desigur cu sursele index.php şi error.phtml şi răsfoind eventual unele fişiere din /usr/share/php/Zend/).

Mai întâi, trebuie să sară în ochi variabila $this, care apare în mai multe locuri în error.phtml. Şi în PHP5 - la fel ca în C++, javaScript şi alte limbaje care suportă OOP - this este rezervat drept referinţă la obiectul curent, în cadrul uneia sau alteia dintre metodele constituente (sau funcţiile membre ale obiectului, cum se mai zice în C++).

Putem vedea uşor care este clasa căreia îi aparţine obiectul $this folosit în error.phtml - folosind funcţia PHP get_class(). Intercalând secvenţa următoare în error.phtml (după <body>):

<?php 
echo "obiectul vizat în '" . basename(__FILE__) . "' aparţine clasei <b>" . get_class($this) . "</b><br>";
?>

şi apoi relansând http://zendapp1.d, obţinem şi această informaţie preţioasă:

     obiectul vizat în 'error.phtml' aparţine clasei Zend_View

Tot aşa - intercalând în error.phtml şi reîncărcând în browser - am putea stabili care sunt proprietăţile şi metodele accesibile prin $this din error.phtml, invocând unele funcţii PHP pentru clase şi obiecte:

<?php
    echo "<b>Proprietăţile membre:</b><br>";
    foreach($this as $key => $val) {
        echo $key . "<br/>";
    }

    echo "<b>Metodele obiectului:</b><br>";
    $methods = get_class_methods($this);
    foreach($methods as $function) {
        echo $function . "<br/>";
    }
?>

Putem obţine la fel, lista tuturor claselor existente (inclusiv cele prefixate de Zend_), folosind $classes = get_declared_classes()… Însă această direcţie de investigare are o natură preponderent informativă (şi aduce a birocraţie - ne apucăm să colectăm dosare de clase, de obiecte şi de metode); numai cu dicţionarul (fără şi regulile aferente), nu vom putea clarifica funcţionarea aplicaţiei.

Din ceea ce găsim pe calea informativă schiţată mai sus, cel mai relevant este faptul că $this (folosit în error.phtml) referă o instanţă a clasei Zend_View. Ca multe alte framework-uri, ZF implementează şablonul (la care ne-am mai referit şi anterior, aici) Model-View-Controller (MVC) - ceea ce este reflectat de la bun început, prin prevederea subdirectoarelor controllers/, models/, views în cadrul folderului creat pentru aplicaţie.

Să ne amintim un termen furnizat în "pagina de eroare": Stack trace. Este vorba de cadrele stivă existente la momentul depistării erorii şi este redat lanţul "subrutinelor" apelate consecutiv, începând din momentul executării fişierului index.php:

#0 /usr/share/php/Zend/View/Abstract.php(876): Zend_View_Abstract->_script('index/index.pht...')
#1 /usr/share/php/Zend/Controller/Action/Helper/ViewRenderer.php(897): Zend_View_Abstract->render('index/index.pht...')
#2 /usr/share/php/Zend/Controller/Action/Helper/ViewRenderer.php(918): Zend_Controller_Action_Helper_ViewRenderer->renderScript('index/index.pht...', NULL)
#3 /usr/share/php/Zend/Controller/Action/Helper/ViewRenderer.php(957): Zend_Controller_Action_Helper_ViewRenderer->render()
#4 /usr/share/php/Zend/Controller/Action/HelperBroker.php(277): Zend_Controller_Action_Helper_ViewRenderer->postDispatch()
#5 /usr/share/php/Zend/Controller/Action.php(523): Zend_Controller_Action_HelperBroker->notifyPostDispatch()
#6 /usr/share/php/Zend/Controller/Dispatcher/Standard.php(289): Zend_Controller_Action->dispatch('indexAction')
#7 /usr/share/php/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
#8 /usr/share/php/Zend/Application/Bootstrap/Bootstrap.php(97): Zend_Controller_Front->dispatch()
#9 /usr/share/php/Zend/Application.php(366): Zend_Application_Bootstrap_Bootstrap->run()
#10 /home/vb/zendApp1/public/index.php(26): Zend_Application->run()
#11 {main}  

Dacă executarea programului s-ar fi încheiat cu succes, atunci stiva de execuţie ar fi fost "curăţată" în final; fiind însă în starea de eroare - subrutina corespunzătoare vârfului stivei de execuţie (marcat în raportul redat mai sus cu "#0") nu a putut fi încheiată, încât nu s-a putut elimina cadrul stivă respectiv (şi drept urmare nici cele aflate dedesubt, revenind adică din #0 în #1, din #1 în #2, ş.a.m.d. până la cadrul #11 de la baza stivei).

Linia #10 /home/vb/zendApp1/public/index.php(26):Zend_Application->run() arată că la început s-a apelat metoda run() a unui obiect de clasă Zend_Application, care a fost lansată de pe linia 26 din fişierul index.php. Şi într-adevăr, în index.php găsim imediat secvenţa edificatoare (am marcat linia 26):

/** Zend_Application */
require_once 'Zend/Application.php';

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
            ->run(); // linia 26 din 'index.php'

Prin această secvenţă, mai întâi este inclus şi evaluat fişierul /usr/share/php/Zend/Application.php (am indicat calea completă, amintindu-ne ce am setat iniţial în include_path); acest fişier defineşte clasa Zend_Application (bineînţeles că este foarte util de văzut cam ce înseamnă aceasta, deschizând fişierul respectiv într-un editor de text). Deschizând şi alte fişiere din Zend/, putem constata direct (altfel, puteam citi şi în manual) că se instituie o corespondenţă bijectivă între numele clasei şi numele fişierului care o defineşte; fiecare fişier conţine definiţia unei singure clase, iar "_" din numele clasei devine "/" pentru calea fişierului.

Apoi, în secvenţa de mai sus, se creează (folosind new, desigur) o instanţă $application a clasei a cărei definiţie tocmai s-a obţinut. În final, prin $autoload->bootstrap()->run() se pun în execuţie una după alta, două metode ale obiectului tocmai creat. Apelul ->bootstrap() nu apare în stiva de execuţie redată sub Stack Trace, ceea ce înseamnă că metoda respectivă s-a executat cu succes (eliminând apoi, cadrul-stivă corespunzător ei).

Acum putem vedea în Zend/Application.php - sau putem deduce direct din Stack Trace linia #9 - că metoda ->run() (de la #10) instanţiază clasa Zend_Application_Bootstrap_Bootstrap (folosind new) şi redă controlul metodei ->run() a noului obiect. Uitându-ne în /Zend/Application/Bootstrap/Bootstrap.php (în jurul linie 97, cum se indică în Stack Trace #8) vedem că (din #9) se invocă mai departe Zend_Controller_Front->dispatch().

Văzând metoda ->dispatch() în Zend/Controller/Front.php ne dăm seama (măcar din comentariul "Dispatch an HTTP request to a controller/action") că aici are loc analiza cererii HTTP curente şi repartizarea în consecinţă a acesteia către o "subrutină" sau alta, care să o rezolve şi să returneze un răspuns. Uitându-ne în Stack Trace, de la #4 spre #0, vedem că răspunsul respectiv este transferat în final unui obiect Zend_View; acesta desigur, va constitui fişierul pe care serverul îl va returna drept răspuns cererii HTTP respective.

Putem sista de-acum analiza: am reuşit într-o măsură suficientă, să evidenţiem principalele obiecte şi metode aferente, care asigură funcţionarea aplicaţiei, între recepţionarea unei cereri HTTP şi rezolvarea acesteia. Am ilustrat cu acest prilej, un procedeu general de investigare independentă a lucrurilor. Trebuie să fie clar totuşi, că pentru a înţelege conceptele implicate, pentru a deprinde tehnicile de lucru specifice, cât şi pentru a sesiza posibilităţile de extindere oferite - va fi necesară şi răsfoirea manual ZF.

Controlleri, Acţiuni şi View-uri

Noi am tot folosit termenul comun "subrutină", dar în termenii MVC (folosiţi şi de ZF) este vorba de o acţiune ("action") definită într-un fişier controller. Să revenim la scriptul zf.php (cu care am creat mai sus zendApp1/), să-l folosim acum pentru a crea un "controller" şi apoi să investigăm (ca de obicei…) rezultatul:

vb@localhost:~/zendApp1$ zf  create  controller  Crud
Creating a controller at /home/vb/zendApp1/application/controllers/CrudController.php
Creating an index action method in controller Crud
Creating a view script for the index action method at /home/vb/zendApp1/application/views/scripts/crud/index.phtml
Creating a controller test file at /home/vb/zendApp1/tests/application/controllers/CrudControllerTest.php
Updating project profile '/home/vb/zendApp1/.zfproject.xml'

Vedem că nu prea avem ce "investiga": s-a afişat clar ce s-a creat şi unde. Fiindcă intenţia era de a face o aplicaţie CRUD, am ales denumirea Crud iar ZF i-a adăugat sufixul "Controller" - reflectând o convenţie generală privind numele controller-ilor (a vedea în imaginea de mai sus IndexController.php şi ErrorController.php).

Operaţiile "Creating" din listingul redat mai sus vizează desigur crearea oricărui controller şi deducem că: orice controller este un fişier .php şi va deţine o "acţiune" cu numele implicit indexAction() (sufixând cu "Action", similar numelui de controller); oricărui controller îi corespunde un subdirector în /views/scripts/, denumit la fel ca numele controllerului (dar fără sufixul "Controller" şi convertit la litere mici) ; oricărei acţiuni trebuie să-i corespundă (în views/scripts/crud/, în cazul nostru) un "view script" denumit la fel ca şi acţiunea (dar fără sufixul "Action", cu litere mici şi având extensia .phtml).

.zfproject.xml menţionat mai sus în final, înregistrează toate componentele aplicaţiei şi deschizându-l cu un editor de text constatăm că figurează aici nu numai componentele existente, dar şi multe altele (cele inexistente, dar posibile fiind specificate prin enabled = "false"). Deducem un lucru, foarte important pentru dezvoltarea aplicaţiilor folosind un framework: nu-i de loc necesar să ne rezumăm la scheletul de aplicaţie standard (furnizat implicit, de către zf create project); putem adăuga în cadrul aplicaţiei noi componente, de diverse categorii (layoutDirectory, formsDirectory, dataDirectory, modulesDirectory, etc.).

Să invocăm acum din browser http://zendapp1.d/crud - obţinem o pagină conţinând View script for controller Crud and script/action name index. Să evocăm cum s-au petrec lucrurile, mizând şi pe cele stabilite prin lunga investigare anterioară.

Hostul zendapp1.d are stabilit ca director rădăcină /public/ (subdirector al lui zendApp1/) şi regula de bază este aceea de a nu satisface decât cereri asupra fişierelor (sau subdirectoarelor) acestuia. Dar în public/ există un fişier .htaccess şi AllowOverride este setată All, ceea ce face să intervină o a doua regulă: pentru oricare cerere HTTP serverul îşi reconfigurează întâi acţiunile, conform directivelor citite din fişierele .htaccess; aceasta face posibilă eventual (dar este de nedorit), deservirea şi a unor fişiere din afara directorului rădăcină (nuanţând astfel, regula "de bază").

Dar în cazul nostru, avem de-a face cu un fişier .htaccess standard, conţinând două reguli RewriteRule obişnuite: prima permite accesul la orice fişier sau subdirector existent în public/, dacă este menţionat ca atare în cererea HTTP - caz în care totul se încheie, răspunzând cu furnizarea fişierului respectiv; dacă prima regulă nu se poate aplica, atunci se trece la următoarea - anume, se pasează cererea respectivă către public/index.php.

De exemplu, dacă am crea un subdirector public/crud şi eventual am copia acolo nişte fişiere - atunci cererea invocată mai sus http://zendapp1.d/crud ar conduce (în baza aplicării primeia dintre regulile din .htaccess) la o pagină "Index of /crud", pe care sunt listate fişierele din subdirectorul creat.

Neavând însă vreun subdirector "crud" în public, se aplică a doua RewriteRule: se evaluează public/index.php, asociind în cele din urmă cererii http://zendapp1.d/crud un obiect Zend_Controller_Request_Http (a vedea mai sus, linia #7 din Stack Trace); se instanţiază clasa care defineşte controllerul (avem class CrudController în fişierul /controllers/CrudController.php), apelând apoi acţiunea implicită a acestuia ->indexAction() (fiindcă în cererea HTTP nu este menţionată şi vreo acţiune, ci numai controllerul); în final, se constituie un obiect Zend_View corespunzător acţiunii tocmai executate, furnizând astfel fişierul views/scripts/index.phtml (sau acel rezultat care se obţine prin evaluarea acestuia, în cazul când el conţine şi secvenţe PHP).

Să nu uităm totuşi un lucru: pe măsură ce sunt executate diversele "subrutine" (apeluri de constructori de obiecte şi de metode ale acestor obiecte), stiva de execuţie (a ne aminti de Stack Trace) "creşte şi descreşte" (la intrarea în subrutină, respectiv la revenirea în subprogramul apelant), iar în final stiva este curăţată - revenind deci în "main", adică înapoi în contextul lui index.php care a stat la baza întregul lanţ de apeluri.

Aceasta înseamnă că lucrurile nu se încheie neapărat, cu redarea fişierului views/scripts/index.phtml (care este pus într-un obiect Zend_Controller_Response_Http înainte de revenirea în index.php); dacă adăugăm în finalul lui index.php o instrucţiune ca de exemplu echo APPLICATION_PATH;, atunci pagina furnizată în final drept răspuns va cuprinde şi rezultatul execuţiei acestei instrucţiuni (pe lângă conţinutul asigurat de către index.phtml).

Constituirea acţiunilor necesare

CrudController.php obţinut mai sus, specifică iniţial numai acţiunea standard "index":

<?php

class CrudController extends Zend_Controller_Action
{

    public function init()
    {
        /* Initialize action controller here */
    }

    public function indexAction()
    {
        // action body
    }


}

Ziceam mai sus, "orice controller este un fişier .php"… Vedem că de fapt, un controller este o clasă PHP (nu "un fişier"), specificând funcţia init() şi diverse alte metode "publice" (acţiunile pe care le "controlează"); este drept însă că în ZF orice fişier .php defineşte cel mult o singură clasă PHP.

Clasa respectivă extinde clasa Zend_Controller_Action (moştenindu-i proprietăţile şi metodele "public" şi "protected"); constructorul acesteia va apela metoda init() a clasei "derivate", care devine astfel acţiunea implicită - este suficient ca în cererea HTTP să fie specificat controllerul, nu neapărat şi acţiunea init.

Să adăugăm în controllerul Crud acţiunile noastre: vrem să listăm înregistrările, să le edităm, etc.; acţiunile pot fi înscrise şi direct în fişier, dar să folosim iarăşi scriptul zf:

vb@localhost:~$ cd zendApp1/
vb@localhost:~/zendApp1$  zf  create  action  list  Crud
Creating an action named list inside controller at /home/vb/zendApp1/application/controllers/CrudController.php
Updating project profile '/home/vb/zendApp1/.zfproject.xml'
Creating a view script for the list action method at /home/vb/zendApp1/application/views/scripts/crud/list.phtml
Updating project profile '/home/vb/zendApp1/.zfproject.xml'

Comanda de mai sus trebuie repetată pentru fiecare acţiune dorită. Acţiunile respective listAction() etc. apar exact ca indexAction() redată mai sus (evident, noi va trebui să completăm "corpul" funcţiilor respective). http://zendapp1.d/crud/list se va finaliza prin furnizarea "view"-ului views/scripts/crud/list.phtml corespunzător acţiunii listAction().

Dacă vrem cumva ca "list" să fie acţiunea lansată "implicit", atunci putem înscrie $this->listAction(); în corpul funcţiei indexAction(); cererea http://zendapp1.d/crud - care specifică numai controllerul, nu şi vreo acţiune - lansează acţiunea implicită "index" şi fiindcă acum aceasta apelează ->listAction(), rezultă în final rezultatul care s-ar fi obţinut prin invocarea explicită ://.../crud/list, a acţiunii "list".