Eräs viime vuosien suurista muutoksista web-kehityksessä on ollut siirtyminen MVC-malliin käyttöön (Model-View-Controller). Yksittäisten skriptitiedostojen sijaan saitteja rakennetaan nykyään kontrollereiden, sivupohjien ja tietorajapintojen päälle. Tämä sai pitkälti alkunsa Ruby on Railsista mutta käytäntö on levinnyt myös PHP:n puolelle. Eräs suosituista MVC-alustoista on Zend Framework, jota olen itse käyttänyt muutamissa projekteissa, kuten Pelikoneessa.
Samalla olen kuitenkin kiinnostunut Drupalista, joka tarjoaa monipuolisemman alustan web-sovelluskehitykselle. Drupalin moduulirajapinta antaa mahdollisuuden kustomoida järjestelmää vapaasti, ja se sopii myös MVC-tyyppisten sovellusten tekemiseen. Pohdin tässä kirjoituksessa hiukan, miten Zend Frameworkin MVC-mallin konseptit voidaan siirtää Drupaliin.
Reitittäminen ja kontrollerit
Web-sovelluksen perustana ovat aina URL-osoitteet ja niiden reitittäminen halutuille kontrollereille. Zend Frameworkin reititys tehdään yleensä Zend_Controller_Router_Route-objekteilla. Niiden osoitteet voivat olla esimerkiksi muotoa /articles/:year/:title, jolloin :year ja :title ovat dynaamisia parametrejä. Reitti johtaa kontrolleriin, joka käsittelee pyynnön parametreineen. Esimerkiksi:addRoute(new Zend_Controller_Router_Route( '/articles/:year/:title', array( 'controller' => 'articles', 'action' => 'index'))); class ArticlesController extends Zend_Controller_Action { function indexAction() { ... } }
Drupalissa reititys perustuu hook_menu()-funktioon. Sillä luodut valinnat pysyvät poissa näkyvistä Drupalin valikoista, kun niiden tyyppi on MENU_CALLBACK. URL-osoitteisiin voidaan liittää parametrejä "page arguments" -muuttujalla. Kontrolleria taas vastaa "page callback"-muuttuja, joka määrittelee halutun funktion pyynnön käsittelijäksi. Esimerkiksi:
function example_menu() { return array( 'articles' => array( 'type' => MENU_CALLBACK, 'page callback' => 'example_articles_index', 'page arguments' => array(1, 2))); }function example_articles_index($year, $title) { … }
Sivupohjat
Zend Frameworkin sivupohjat perustuvat kaksitasoiseen järjestelmään. Koko saitin yhteisenä sivupohjana (Zend_Layout) on yleensä vain yksi HTML-tiedosto, jonka sisään muut pohjat renderöidään tilanteen mukaan. Drupalissa tätä vastaa teeman tiedosto page.tpl.php.Toisella tasolla Zend Frameworkissa käytetään näkymiä (Zend_View). Tavallisesti saitin jokaisen kontrollerin jokaista actionia vastaa yksi näkymätiedosto. Ylempänä käytetyn esimerkin mukaisesti näkymä olisi nimeltään action/index.phtml. Zend renderöi kyseisen näkymän automaattisesti, ellei sitä erikseen ohiteta kontrollerissa.
Drupalissa näkymät täytyy itse määritellä hook_theme()-funktiolla ja renderöidä sitten theme()-funktiolla. Artikkeliesimerkki voitaisin toteuttaa näin, olettaen että näkymä on tiedostossa articles-index.tpl.php:
function example_theme() { return array( 'articles-index' => array( arguments = array('year' => null, 'title' => null), template => 'articles-index')); }function example_articles_index($year, $title) { theme(‘articles-index’, $year, $title); }
Tietovarasto
Viimeisenä MVC-mallin osana tarvitaan vielä keino mallintaa tietokantaan tallennettua dataa. Tässä suhteessa Drupalin voisi sanoa olevan Zend Frameworkia paljon kehittyneempi, sillä ZF tarjoaa vain melko yksinkertaisen rajapinnan tietokannan yksittäisiin tauluihin (Zend_Db_Table) ja niiden riveihin (Zend_Db_Table_Row). Esimerkkimme artikkelit voisivat toimia tähän tyyliin:class Articles extends Zend_Db_Table_Abstract { protected $_name = 'articles'; protected $_rowClass = 'Article'; function fetchArticle($id) { ... } } class Article extends Zend_Db_Table_Row_Abstract { function save() { ... } function delete() { ... } }
Drupalissa uudet tietotyypit pohjautuvat node-objekteihin. Uudentyyppisiä objekteja lisätään toteuttamalla hook_node_info()-funktio, joka palauttaa moduulin tukemat sisältötyypit. Lisäksi tarvitaan hook_insert()-, hook_update()-, hook_delete()- ja hook_load()-funktiot artikkelitiedon tallentamiseen, päivittämiseen, poistamiseen ja lataamiseen tietokannasta.
function example_node_info() { return array( 'article' => array( 'name' => t('Article'), 'module' => 'example', 'description' => t('An article item'))); }function example_insert($node) { … }
function example_update($node) { … }
function example_delete($node) { … }
function example_load($node) { … }
Tietojen hallinta
Zend Frameworkissa kullekin tietotyypille luodaan oma luokka, jonka kautta kyseisiä objekteja ladataan, luodaan, muokataan tai poistetaan tietokannasta. Operaatiot ovat tämän tyyppisiä:// Load $articles = new Articles(); $article = $articles->fetchArticle($id); // Create $article = $articles->fetchNew(); $article->title = 'Uusi otsikko'; $article->save(); // Save $article = $articles->fetchArticle($id); $article->title = 'Uusi otsikko'; $article->save(); // Delete $article = $articles->fetchArticle($id); $article->delete();
Drupalissa näihin operaatioihin käytetään geneerisiä node_load()-, node_save()- ja node_delete()-funktioita. Ne toimivat samoin tietotyypistä riippumatta, kunhan tiedetään node ID.
// Load $article = node_load($id);// Create $node = new stdClass(); $node->type = ‘article’; $node->title = ‘Uusi otsikko’; … $node = node_submit($node); node_save($node);
// Modify $article = node_load($id); $article->title = ‘Uusi otsikko’; node_save($article);
// Delete node_delete($id);
Noodien esittäminen Drupalissa
Drupalissa on vielä yksi ominaisuus, jota Zend Frameworkista ei löydy: se renderöi oletusarvoisesti jokaisen tietokantaan tallennetun noodin webbisivuksi käyttäen node.tpl.php-sivupohjaa. MVC-mallia toteutettaessa ei siis välttämättä tarvita lainkaan hook_menu()-funktiota ja kontrollereita, vaan sama asia voidaan tehdä esimerkiksi nimeämällä noodien URL-osoitteet sopivasti pathauto-moduulilla.Tässä lähestymistavassa hook_view() toimii kontrollerina, joka valmistelee noodin tiedot esitettäväksi webbisivulla. Yksinkertaisimmillaan se näyttää tällaiselta:
function example_view($node, $teaser=false, $page=false) { $node = node_prepare($node, $teaser); return $node; }