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