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.
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) {
...
}
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);
}
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) {
...
}
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);
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;
}
Tämän kirjoituksen tarkoituksena oli todeta, että MVC-mallia voidaan soveltaa yhtä lailla Drupalissa kuin Zend Frameworkissakin. Lähestymistavoissa on pieniä eroja varsinkin tietomallitasolla, mutta taustalla olevat perusteet ovat hyvin samankaltaiset.
Comments
Vesa
Mukavaa ja harvinaista lukea Drupal tekstiä Suomeksi. Tule ihmeessä tutustumaan muihin Drupal-tyyppeihin Drupal.fi:ssä jos et ole sitä vielä tehnyt. :-)