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

Yhteenveto

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.
Published 24.8.2008