9 Edistynyt PHP-ohjelmointi Luentokerran tavoitteena on käydä läpi joukko sellaisia PHP-sovelluksen toteuttamiseen liittyviä tekijöitä, joiden avulla voidaan parantaa verkkopalvelun totetustyön tuottavuutta sekä lopputuloksen uudelleenkäytettävyyttä, päivitettävyyttä, laajennettavuutta ja hallittavuutta Luentokerran aikana käydään läpi seuraavat edistyneen PHP-ohjelmoinnin menetelmät: Malli-Näkymä-Ohjain -arkkitehtuurimalli eli tutummin Model-View-Controller (MVC) -malli ja mallin soveltaminen PHP-kielessä PHP ja ulostulon puskurointi (output buffering) Apache HTTP-palvelin ja URI-tunnisteiden uudelleenkirjoitus Lähdemateriaali (ei suoraan jaossa Webissä): Sweat, J. E. An Introduction to MVC Using PHP, PHP Architect, May 2003. James, P. Output Buffering What is it? How does it work? Why do I care?, PHP Architect, May 2003. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 128
Verkkopalvelun yleinen arkkitehtuuri Verkkopalvelun arkkitehtuuri muodostuu sovellusalueesta riippumatta kolmesta kokonaisuudesta: 1. Verkkopalvelun pysyvän tietosisällön varastointi (persistent data storage) 2. Verkkopalvelun toimintovuon (application flow) toteuttaminen sekä tiedon ja toimintojen käyttöoikeuksien tarkastaminen 3. Tietosisällön esittäminen käyttäjälle Malli Liiketoimintalogiikka Näkymä Esitystapalogiikka Ohjain Toimintovuon hallinta Kertausta: koska verkkopalveluista löytyy sovellusalueesta riippumatta verkkopalvelusta toiseen toistuvia osia, voidaan erilaisia ratkaisumalleja kierrättää sovelluksesta toiseen ideoiden, toimintatapojen tai konkreettisten toteutusten tasolla Eräs yleinen toimintatapa on Model-View-Controller -malli tai suomalaisittain Malli- Näkymä-Ohjain -malli, joka jakaa sovelluksen teknisen arkkitehtuurin kolmeen kokonaisuuteen, liiketoimintalogiikasta vastaavaan malliin, toimintovuon hallinnasta vastaavaan ohjaimeen ja esitystapalogiikasta vastaavaan näkymään. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 129
MVC-malli Malli-Näkymä-Ohjain (Model-View-Controller) -malli eli MVC-malli on 1970-luvulla Xeroxin tutkimuslaboratoriossa kehitetty arkkitehtuurimalli graafisiin käyttöliittymiin perustuvien sovellusten toteuttamiseen. Suunnittelumallilla (design pattern) tarkoitetaan yleistä, täsmällisesti määriteltyä ratkaisumallia, jota voidaan soveltaa erilaisiin ongelmiin. MVC-malliin viitataan usein suunnittelumallina. MVC ei kuitenkaan varsinaisesti ole suunnittelumalli vaan yleisempi filosofia siitä, millä tavalla sovelluksen ohjelmistoarkkitehtuuri (software architecture) rakentuu. Verkkopalveluissa MVC-malli on käytössä etenkin Java-teknologiaan perustuvissa toteutuksissa. Mallin soveltaminen on kuitenkin mahdollista myös PHP-sovelluksissa. Suunnittelumallit Model 1 ja Model 2 ovat Java-teknologiasta tuttuja MVC-mallin sovelluksia. Java-sovelluksissa Model 2 -mallin mukainen toteutus rakennetaan yleensä Struts -sovelluskehyksen (http://struts.apache.org/) pohjalta. MVC-mallin mukaiset toteutukset perustuvat PHP-kielessä Javan tavoin luokkiin ja olioihin. Nyt MVC-mallia tarkastellaan kurssin rajauksen mukaisesti yleisemmällä tasolla modulaarisuuden näkökulmasta. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 130
MVC-malli: malli ja näkymä Malli (Model) vastaa MVC-mallin mukaisessa toteutuksessa sekä pysyvän tietosisällön varastoinnista että tietosisällön muokkaamiseen liittyvien sääntöjen toteuttamisesta. Usein sanotaan, että malli toteuttaa sovelluksen liiketoimintalogiikan (business logic). Malli peittää tietovaraston teknisen toteutuksen muulta sovellukselta. Malli on ainoa osa sovellusta, joka on yhteydessä tietovarastoon. Tietosisältö voidaan sijoittaa esimerkiksi relaatiotietokantaan, istuntoon tai määrämuotoisiin tiedostoihin. Malleja on yhdessä sovelluksessa tyypillisesti useita. Esimerkkejä malleista ovat suunnitteluratkaisuista riippuen esimerkiksi käyttäjä, julkaisukonteksti tai ostoskori. Näkymä esittää sovelluksen tietosisällön käyttäjälle. Verkkopalvelussa yleisin tiedon esitysmuoto on luonnollisesti HTML. MVC-mallin mukaisesti voidaan kuitenkin tuottaa tietoa myös esimerkiksi teksti- tai XML-muodossa tai vaikkapa kuvina. MVC-malli voidaan toteuttaa esimerkiksi siten, että käyttöliittymä pyytää mallilta tarvittavat tiedot ja tuottaa tietojen perusteella lopullisen näkymän Nykyaikaisen WWW-arkkitehtuurin käsitteillä näkymä pyytää resurssin tiedot mallilta ja luo tietojen perusteella resurssin representaation. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 131
MVC-malli: ohjain Ohjain (Controller) tai kontrolleri toimii MVC-mallin mukaisen sovelluksen sydämenä ja aivoina Ohjain ottaa vastaan sovellukselle esitetyt pyynnöt. Yleisessä tapauksessa pyyntöjä voidaan HTTP-protokollan ohella esittää vaikkapa komentoriviltä. Pyynnön perusteella ohjain 1) muuttaa tarvittaessa sovelluksen tilaa mallin avulla ja 2) valitsee asiakkaalle välitettävän näkymän. Toimintojen jako mallin, näkymän ja ohjaimen vastuulle ei ole yksikäsitteinen: Esimerkiksi käyttäjän syötteen oikeellisuuden tarkastaminen voi tapauksesta riippuen kuulua joko ohjaimen tai mallin tehtäviin Mallin ja näkymän suhteen jako on kuitenkin pääsääntoisesti selkeä: malli ei ikinä sisällä HTML-merkkausta eikä näkymä tietokannan suoraa käsittelyä. Oliopohjaisessa ratkaisussa sovelluksen abstraktiotasoa voidaan mallin, näkymän ja ohjaimen ohella nostaa määrittelemällä luokat toiminnoille (Action), lomakkeille (Form) ja eteenpäinohjauksille (Forward). Eteenpäinohjaukset voidaan määritellä esim. XML-muotoisessa asetustiedostossa MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 132
MVC-malli, PHP ja Web-tekniikat Sweat (2003) jäsentää PHP-kieleen perustuvan verkkopalvelun toteutustekniikat MVCmalliin havainnollisella tavalla: $_SESSION $_COOKIE $_FILES DB tiedostonkäsittely XML-RPC SOAP Malli Liiketoimintalogiikka HTML Näkymä WML Esitystapalogiikka XML XSLT teksti, kuvat & muut MIME-tyypit HTTP($_GET, $POST, $_REQUEST) komentorivi Ohjain Toimintovuon hallinta Kertaus: ohjain ottaa pyynnöt vastaan ja käskyttää niiden perusteella mallia ja näkymiä. Malli vastaa pysyvän tiedon käsittelystä ja varastoinnista ja näkymä tiedon esittämisestä. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 133
MVC-malli ja toiminnon luonne Sweat jakaa toiminnot kahteen tyyppiin: näkymän valinta (vrt. turvallinen toiminto WWW-arkkitehtuurissa) ja sovelluksen tilan muuttamisen (vrt. ei-turvallinen toiminto): Pyyntö Vastaus Asiakas 7 1 Palvelin Ohjain 2 Näkymä 6 3 Malli 5 4 DB Pyyntö 1 Ohjain 3 7 Vastaus Asiakas 6 5 Palvelin Malli 2 4 DB Pyyntö MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 134
Sivupolku: ulostulon puskurointi Ulostulon puskurointi (output buffering) on PHP-kielen sisäänrakennettu ominaisuus, joka mahdollistaa sovelluksen toteuttamisen siten, että sovelluksen tuloste kootaan ensin sovelluksen muistiin ja lähetetään lopuksi kerralla pyynnön tehneelle asiakkaalle Eräs hyöty ulostulon puskuroinnista on seuraavan virheilmoituksen välttäminen: Warning: Cannot add header information - headers already sent Virheilmoitus johtuu siitä, että sovellus on jo ehtinyt aloittaa HTTP-vastauksen sisällön lähettämisen asiakkaalle ja yrittää vielä lisätä uuden otsikon (header) HTTPvastaukseen. Ulostulon puskurointi varmistaa sen, että kaikki otsikot ehditään asettamaan ennen dokumentin sisällön tulostamista. James esittää artikkelissaan joukon käyttötapauksia ulostulon puskuroinnille: sovelluksen suorituksen keskeyttävien virheiden käsittely, sovelluksen tulostaman HTML-merkkauksen tai sisällön siistiminen, asiakkaalle lähetettävän tiedon pakkaaminen (HTTP 1.1), näkymien tuottaminen XSL-muunnoksilla ja istunnon URItunnisteessa esitetyn istunnon tunnisteen sijoittaminen HTML-lomakkeeseen Suuri osa Jamesin esimerkeistä voidaan kuitenkin hoitaa sovelluksen elegantilla suunnittelulla ja toteutuksella ilman ulostulon puskurointia MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 135
Ulostulon puskuroinnin toteuttaminen Seuraavat PHP-kielen kirjastofunktiot ovat hyödyllisiä ulostulon puskuroinnissa: ob_start(): Käynnistää ulostulon puskuroinnin. Parametrina voidaan antaa funktion nimi, jolle puskurin sisältö tyhjennettäessä välitetään. ob_flush(): Tulostaa ja tyhjentää kerätyn puskurin. Funktion avulla voidaan esimerkiksi huolehtia tulostuksen hallitusta vaiheistamisesta. ob_clean(): Tyhjentää puskurin tulostamatta sitä. Voidaan hyödyntää esimerkiksi virheenkäsittelyssä. ob_get_contents(): Palauttaa puskurin sisällön esimerkiksi muuttujaan sijoitettavaksi. ob_end_clean(): Tyhjentää puskurin ja päättää ulostulon puskuroinnin. Voidaan käyttää ob_get_contents()-funktion parina. ob_end_flush(): Tulostaa puskurin ja päättää ulostulon puskuroinnin. PHP-tulkki kutsuu itse ob_end_flush()-funktiota ohjelman suorituksen päätteeksi, joten sen kutsuminen ei ole pakollista. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 136
URI-tunnisteiden uudelleenkirjoitus Verkkopalvelun MVC-mallin mukainen toteutus johtaa siihen, että näkymät yksilöidään yksikäsitteisten URI-tunnisteiden sijaan HTTP-pyynnön parametreilla. Esimerkiksi hakukoneet eivät yleensä indeksoi kattavasti näkymiä, joiden yksilöimisessä on käytetty pyynnön parametreja (vrt. listauksen järjestäminen eri ominaisuuksien perusteella). Parametrit aiheuttavat ongelmia myös niille käyttäjille, jotka navigoivat sovelluksessa URI-tunnistetta muokkaamalla. Kolmas parametrien aiheuttama ongelma ilmenee silloin, kun sivusto halutaan tallentaa eräajona levylle: näkymillä ei ole yksikäsitteisiä nimiä Apachen mod_rewrite-moduuli esittelee mahdollisuuden URI-tunnisteiden uudelleenkirjoittamiseen siten, että pyynnön parametreilla yksilöidyt näkymät näkyvät verkkopalvelun ulkopuolelle hakemistopolkuna Mahdollisuus URI-tunnisteiden uudelleenkirjoittamiseen on varsin keskeinen ominaisuus, joka kannattaa ottaa huomioon toteutustekniikan valinnassa. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 137
Esimerkki: tekstiartikkelin yksilöinti Viikon harjoituksissa käsitellään sovellusta, jolla käyttäjät voivat tehdä hakuja tekstiartikkeleihin ja lukea niitä. Oletetaan, että erään artikkelin tunniste on hal9000fi. Artikkeli avataan selaimessa kirjoittamalla osoitekenttään URI-tunniste: http://hmopetus.ee.tut.fi/~huhtis/show-article.php?articleid=hal9000fi Apachen HTTP-palvelimelle voidaan kirjoittaa ohjeet, jonka perusteella se jäsentää ja kirjoittaa uudelleen HTTP-pyynnön mukana välitetyn URI-tunnisteen. Uudelleenkirjoittaville URI-tunnisteille määritellään hahmo säännöllisellä lausekkeella (ote tiedostosta.htaccess): RewriteEngine On RewriteRule article-(.*).php /~huhtis/show-article.php?articleid=$1 Nyt tekstiartikkeli voidaan ladata selaimeen kirjoittamalla osoitekenttään URI-tunniste http://hmopetus.ee.tut.fi/~huhtis/article-hal9000fi.php Käyttäjäystävällisten URI-tunnisteiden toteuttaminen edellyttää URI-tunnisteiden uudelleenkirjoittamisen ohella sitä, että sisällöntuottajat määrittelevät tietoalkioiden yksikäsitteiset tunnisteet käsin (= eksplisiittisesti) MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 138
Lopuksi Kuten jo aikaisemmin mainittiin, paras tapa MVC-mallin mukaiseen toteutukseen on aitoon olio-ohjelmointiin perustuva ratkaisu. PHP5 tarjoaa välineet tähän: luokat, periytyminen ja esimerkiksi poikkeukset (exception) mahdollistavat vankan arkkitehtuurin suunnittelun ja toteuttamisen verkkopalvelulle Oliopohjaisen lähestymistavan yhdistäminen PHP-kielen voidaan perustellusti kyseenalaistaa, sillä oliopohjaisia teknologioita on jo olemassa. Toisaalta oliopohjaisuuden avulla voidaan merkittävästi parantaa PHP-toteutuksen laatua. Toteutustyön pohjaksi valittavasti teknologiasta riippumatta MVC-mallin mukaisen arkkitehtuurin toteuttaminen tyhjästä on yleensä turhaa, sillä soveltajien käytössä on joukko valmiita kehyksiä, joiden pohjalta oma sovellus voidaan toteuttaa Huomaa myös, että MVC ei ole ainoa suunnittelumalli (tai arkkitehtuurimalli) maailmassa. Esimerkiksi suunnittelumallien paksu kirja Gamma, E., Helm, R., Johnson, R., Vlissides, J. Design Patterns on ehdottomasti lukemisen arvoinen. Suunnittelumalleja ja niiden hyödyntämistä sovellusten toteuttamisessa käsitellään TTY:ssä opintojaksolla Ohjelmistoarkkitehtuurit (http://www.cs.tut.fi/~ohar/). Seittiohjelmoinnissa käsitellään tarkemmin verkkopalvelun toteuttamista MVC-mallin mukaisesti Java-teknologialla. MATHM-57100 Hypermedian ohjelmointi (kevät 2006) 139