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.
Osui silmään Discovery Channelilla: RoboCup 2007 Final [YouTube]. Nämä on paljon hienompia kuin ne kauko-ohjattavat robottisodat..
Robottifudiksessa ideana on siis, että että laitteet ovat täysin itseohjautuvia ja yrittävät tehdä maaleja oranssia palloa potkimalla. Hienointa on, miten robotit kävelemisen lisäksi nousevat näppärästi pystyyn kaaduttuaan.
Tässä ensimmäinen ongelma IPv6- ja IPv4-maailmojen yhteensopivuudessa: reCAPTCHA ei toimi. Selitys on aika yksinkertainen: se yrittää pitää kirjaa käyttäjistä IP-osoitteiden perusteella, ja ne eivät täsmää, koska käyttäjä tulee omalle saitilleni IPv6:sta ja reCAPTCHAn saitille IPv4:stä.
Olen käyttänyt reCAPTCHAa koska Venäjältä tulee jatkuvasti kommenttispammia, joka menee läpi siitä yksinkertaisesta matematiikkakyselystä. Täytyy katsoa saako sen korjattua IPv6:n puolelle jotenkin. Tai sitten keksiä keino käyttää Drupalissa erilaisia moduuleja riippuen siitä, kummasta verkosta käyttäjä tulee.
Tulipa ostettua Songs for Tibet iTunesista, kun siitä niin metelöidään. Näyttää olevan myös Amazonissa myynnissä. (Tuote on tilapäisesti loppu ja biisilista on väärin, mutta kansi kyllä täsmää.)
Levynä tuo on aika tylsä (Underworldin hidasta sekoilubiisiä myöten), mutta eipä kai se olekaan se pointti...
Tämä kotisivustoni on nyt näkyvissä myös IPv6-verkon puolella osoitteessa ipv6.kfalck.net tai 2002:3e4e:defd::1. Kuten osoitteesta huomaa, yhteys kulkee FUNETin 6to4-välityspalvelun kautta, joten ihan "oikea" IPv6 se ei vielä ole.
Pelkkä kfalck.net vastaa DNS:ssä sekä A- että AAAA-pyyntöihin. Saitin yläreunassa pitäisi näkyä pienellä "(IPv6)" jos yhteys on tullut sitä kautta. Mahtaakohan kellään toimia?
Viime aikoina on puhuttu paljon IPv4-osoitteiden ehtymisestä lähivuosina. Se sai minutkin taas kokeilemaan miten hyvin IPv6 toimii kotona.
Koska kytkeydyn nettiin Airport Extreme -tukiasemalla, IPv6 oli aika helppo ottaa käyttöön. Käytännössä pitää vain valita tukiaseman asetuksista Advanced / IPv6 / IPv6 Mode: Tunnel. Tämän jälkeen Airport alkaa automaattisesti tunneloida kotiverkossa olevien koneiden IPv6-liikenteen Internettiin 6to4:llä. Sen ideana on, että ISP:n ei tarvitse mitenkään tukea IPv6:ta tässä vaiheessa. Oletuksena Airport blokkaa sisääntulevat yhteydet palomuurissaan, joten IPv6-tunnelointi vastaa tietoturvaltaan NATia.
Kotiverkossa olevista Maceista on myös helppoa enabloida IPv6. Pitää vain valita verkkoyhteyden TCP/IP-asetuksista Configure IPv6: Automatically. Tämän jälkeen kone saa tukiasemalta osoitteen automaattisesti.
Windows XP:ssä toimenpide on hieman monimutkaisempi. Verkkoyhteyden yleisiin asetuksiin pitää valita Install... / Protocol / Microsoft TCP/IP version 6. Sen jälkeen kone käynnistyy uudelleen ja IPv6 on valmis käytettäväksi.
Mistä näkee, että IPv6 toimii? Helpointa on avata selaimella sivu www.apnic.net. Se kertoo ylänurkassa, mistä osoitteesta liikennöit. Jos siinä näkyy vanhanaikainen x.x.x.x IPv4-osoite, jotain on pielessä. Muuten taas pitäisi näkyä xxx:xxx:xxx:xxx:xxx:xxx:xxx:xxx-tyyppinen IPv6-osoite. Tyypillisin ongelma on, että Firefoxista on asetettu network.dns.disableIPv6 arvoksi true. Tämän voi tarkistaa about:config-asetuksista ja vaihtaa sen falseksi.
Itselläni Firefox ei jostain syystä halua käyttää IPv6:ta Windowsissa, vaikka se hakeekin AAAA-osoitteet saiteille. Silloin voi vielä kokeilla käyttää suoraan IPv6-osoitetta URLissa tähän tapaan: http://[2001:4860:0:1001::68].
Ylempänä mainitsemani 6to4 muistuttaa idealtaan perinteistä osoitemuunnosta. Kotikäytössähän NAT toimii yleensä niin, että tukiasemalla on yksi julkinen IP-osoite. Liikenne ohjataan kodin sisällä eri koneille sen perusteella, mihin porttiin se kohdistuu.
6to4:ssä tukiasemalla on niinikään yksi julkinen IPv4-osoite. Mutta porttien sijaan kotiverkon koneet erotellaan toisistaan niiden IPv6-osoitteiden perusteella. Kun tukiasema lähettää kotiverkon liikennettä julkiseen nettiin, se kapseloi liikenteen protocol type 41 eli 6in4-paketeiksi, joissa tuo osoite liikkuu mukana. Vastaukset tulevat samanlaisina paketteina, joista paluuosoite voidaan purkaa ja ohjata oikealle kotikoneelle puolella.
Mistä tukiasema tietää, minne enkapsuloidut 6in4-paketit pitäisi julkisen Internetin puolella lähettää? Tätä varten on luotu anycast-osoite 192.88.99.1, joka ohjautuu automaattisesti lähimpään saatavilla olevaan 6to4-relay-reitittimeen. Suomessa se on normaalisti FUNETin IPv6-välityspalvelu, joka on suoraan kytköksissä FICIXiin.
Julkisen IPv6-Internetin puolella puolestaan nämä 6to4-osoitteet ovat aina aliverkossa 2002::/16, joten ne reitittyvät sen perusteella takaisin 6to4-välityspalvelimelle. Osoitteen seuraavat 32 bittiä sisältävät kotikäyttäjän IPv4-osoitteen, ja sen mukaan liikenne on helppo reitittää alkuperäiseen tukiasemaan. Viimeiset 64 bittiä ovatkin sitten yksittäisen kotikoneen MAC-osoite, mikä yksilöi osoitteen lopullisesti.
Nyt myös Facebookissa: Carrotmob Helsinki.
Tässä video Carrotmobin synnystä ja ideasta, kannattaa vilkaista:
Carrotmob Makes It Rain from carrotmob on Vimeo.