doctrine 2 orm ja zend framework 2 sovelluksen … · 2018. 10. 2. · doctrine 2 orm and zend 2...
TRANSCRIPT
DOCTRINE 2 ORM JA ZEND FRAMEWORK 2 SOVELLUKSEN JATKOKEHITYKSEN
VÄLINEINÄ
Case: Matkalaskujärjestelmä
Juho Pentinmikko
Opinnäytetyö Toukokuu 2016
Tietojenkäsittelyn koulutusohjelma
TIIVISTELMÄ
Tampereen ammattikorkeakoulu Tietojenkäsittelyn koulutusohjelma PENTINMIKKO, JUHO: Doctrine 2 ja Zend Framework 2 sovelluksen jatkokehityksen välineinä Case: matkalaskujärjestelmä Opinnäytetyö 52 sivua Toukokuu 2016
Opinnäytetyöni tavoitteena oli tutkia Doctrine 2 ORM -oliorelaatiomapperin sekä Zend Framework 2 sovelluskehyksen soveltuvuutta jo olemassa olevan sovelluksen jatkoke-hitystyöhön. Opinnäytetyön toimeksiantajana oli Evolvit Oy, jonka asiakas X oli uudis-tamassa toiminnanohjausjärjestelmäänsä kuuluvaa matkalaskuraportointia. Opinnäyte-työni tarkoituksena oli saada toimeksiantajan käyttöön työsuunnitelma, jota noudatta-malla matkalaskuraportointi voitaisiin uudistaa. Opinnäytetyö toteutettiin tapaustutki-muksena, jonka esimerkkitapauksena toimii edellä mainittu matkalaskujärjestelmä. Opinnäytetyössä on perehdytty Doctrine 2:n ja Zend Framework 2:n keskeisimpiin ominaisuuksiin sekä kummankin työkalun avulla saavutettavissa oleviin hyötyihin. Opinnäytetyön päätelmistä voidaan todeta, että kummatkin tutkimuksen kohteena olleet työkalut soveltuivat jatkokehitykseen hyvin ja helpottavat ohjelmistokehittäjän työtä: Doctrine 2 vapauttaa kehittäjän rutiininomaisten tietokantakyselyiden kirjoittamiselta ja mahdollistaa helpon tavan tallentaa olioita suoraan tietokantaan. Zend Framework 2:n avulla taas voidaan jakaa ohjelman rakenne helposti MVC -mallin mukaisiin osiin. Opinnäytetyön tuloksena Evolvit Oy:n asiakkaalle saatiin suunniteltua tämän haluama uudistus matkalaskujen syöttämistä varten.
Asiasanat: php, doctrine 2, zend framework 2, oliorelaatiomappaus
ABSTRACT
Tampereen ammattikorkeakoulu Tampere University of Applied Sciences Degree programme in Business Information Systems PENTINMIKKO, JUHO: Doctrine 2 ORM and Zend 2 Framework in Further Development of a Software Appli-cation Case: Travel Expense Report Bachelor's thesis 51 pages, appendices 0 pages April 2016
The goal of this thesis was to examine the applicability of Doctrine 2 ORM object rela-tion mapper and Zend Framework 2 in further development of a pre-existing software application. This thesis was commissioned by Evolvit Oy. The company has a customer who was in need of revising to their ERP system’s travel expense reporting component. This thesis was carried out as a case study. The case studied was the aforementioned revision of the travel expense reporting component. This thesis presents the key features of Doctrine 2 ORM and Zend Framework 2 and the advantages in the development process that are possible to attain by using these tools. As a conclusion of this thesis it can be stated that both of the tools that were examined were well suited for further development of a pre-existing software application: Doc-trine 2 frees the programmer from the tedious task of writing routine database queries and also makes the process of storing object data straight into database relatively easy. With the use of Zend Framework 2 the program structure can be divided into compo-nents according to the MVC design pattern. A plan for the execution of the desired improvements was conceived as a result of this thesis.
Key words: php, doctrine 2, zend framework 2, object relational mapping
4
SISÄLLYS
1 JOHDANTO ................................................................................................................ 7
2 OLIO-RELAATIOMALLINNUS ............................................................................... 8
2.1 Relaatiomalli ........................................................................................................ 8
2.2 Oliomalli ja olio-ohjelmointi ............................................................................. 11
2.3 Olio-relaatiomappaus ......................................................................................... 15
3 Doctrine 2 ORM ........................................................................................................ 18
3.1 Doctrine 2:n esittely ........................................................................................... 18
3.2 Doctrine 2:n perustoiminnot .............................................................................. 18
3.3 Doctrine 2:n EntityManager .............................................................................. 19
3.4 Doctrine 2:n konfigurointi ................................................................................. 20
3.5 Doctrine 2:n entiteetit ........................................................................................ 21
3.6 Doctrine 2:n annotaatiot ..................................................................................... 22
3.7 Doctrine 2:n Entity Repository .......................................................................... 25
3.8 Doctrine 2:n proxyluokat ................................................................................... 26
3.9 Doctrine 2:n komentorivityökalut ...................................................................... 28
3.9.1 Orm:convert ............................................................................................ 28
3.9.2 Orm:generate ........................................................................................... 28
3.9.3 Orm:schema-tool ..................................................................................... 29
3.9.4 Orm:schema-validate .............................................................................. 29
4 ORM:N HYÖDYT JA HAITAT ............................................................................... 30
4.1 Hyödyt ............................................................................................................... 30
4.1.1 Ajan säästäminen ja turhan työn välttäminen ......................................... 30
4.1.2 Ylläpidettävyyden helpottuminen ........................................................... 31
4.1.3 Tietokantariippumattomuus .................................................................... 31
4.2 Haitat .................................................................................................................. 31
4.2.1 Liika abstraktio........................................................................................ 31
4.2.2 Virheen etsinnän vaikeutuminen ............................................................. 31
4.2.3 Suorituskykyyn liittyvät asiat .................................................................. 32
5 ZEND FRAMEWORK 2 .......................................................................................... 33
5.1 Yleistä sovelluskehyksistä ................................................................................. 33
5.2 MVC-suunnittelumalli ....................................................................................... 33
5.3 Zend Framework 2:n esittely ............................................................................. 35
5.4 Zend Framework 2:n moduuli ........................................................................... 36
5.5 MVC -malli ja Zend Framework 2 .................................................................... 36
6 ASIAKKAAN ONGELMA ...................................................................................... 38
6.1 Asiakkaan X toiminnanohjausjärjestelmä ......................................................... 38
5
6.2 Järjestelmän nykytila ......................................................................................... 38
6.2.1 Ohjelmakoodi .......................................................................................... 38
6.2.2 Tietokanta ................................................................................................ 39
6.3 Kilometrikorvausten laskennassa ilmennyt vika ............................................... 40
6.4 Kilometrikorvauksien laskennan korjaaminen .................................................. 41
6.5 Muutostöiden toteuttaminen .............................................................................. 43
6.5.1 Tietokanta ja entiteetit ............................................................................. 43
6.5.2 Tiedon validointi ..................................................................................... 43
6.5.3 Näkymät .................................................................................................. 45
6.5.4 Kontrollerit .............................................................................................. 48
7 JOHTOPÄÄTÖKSET JA POHDINTA .................................................................... 49
7.1 Doctrine 2 .......................................................................................................... 49
7.2 Zend 2 Framework ............................................................................................. 50
7.3 Pohdintaa ........................................................................................................... 51
LÄHTEET ....................................................................................................................... 52
6
LYHENTEET JA TERMIT
ANNOTAATIO Metadataa sisältävä dokumenttilohko
DQL Doctrine Query Language, Doctrine 2:n kyselykieli
DDL Data Definition Language, tietokannan määrittelyyn käytetty
SQL-kielen osa
ENTITEETTI Tietokannan taulua vastaava luokka, josta voidaan muodos-
taa olioita, jotka voidaan tallentaa suoraan tietokantaan
Doctrine 2:n avulla
HYDRATE Täyttää olio tietokannasta löytyvällä tiedolla
JSON JavaScript Object Notation
ORM Object-Relation Mapping
MVC Model-View-Controller -suunnittelumalli eli malli-näkymä-
käsittelijä -suunnittelumalli
YAML YAML Ain’t Markup Language, ihmisluettava datan seria-
lisointikieli
7
1 JOHDANTO
Tämän opinnäytetyön tavoitteena on tutkia Doctrine 2 ORM olio-relaatiomapperin ja
Zend Framework 2:n toimintaa sekä selvittää näiden työkalujen tarjoamia mahdolli-
suuksia ja soveltuvuutta jo olemassa olevan sovelluksen jatkokehityksen välineinä.
Opinnäytetyössäni käyn läpi lyhyesti relaatiomallin ja oliomallin perusteet, kuinka nä-
mä liittyvät toisiinsa, mitä ongelmia relaatiomallin ja oliomallin välillä on. Selvitän
myös mitä tarkoittaa olio-relaatiomallinnus yleisellä tasolla. Tarkoituksena on antaa
selkeä kuva siitä, mihin ongelmaan olio-relaatiomallinnus tarjoaa ratkaisun.
Tämän lisäksi selvitän esimerkkien avulla Doctrine 2 ORM -olio-relaatiomapperin sekä
Zend Framework 2 sovelluskehyksen keskeisimpiä toimintoja. Samalla tehdään katsaus
osista, joista työkalut rakentuvat. Doctrine 2:ta käsittelevien kappaleiden lopuksi käyn
läpi muutaman yleisesti esitetyn mielipiteen ORM -tuotteiden hyödyistä ja myös muu-
tamia kriittisempiä näkökulmia asiaan liittyen.
Työkalujen kuvailun jälkeen käydään läpi työpaikallani Evolvit Oy:llä eteen tullut esi-
merkkitapaus, johon ratkaisumalli suunnitellaan edellä mainittuja työkaluja käyttäen.
Esimerkkitapauksen lähtökohtana toimii Evolvit Oy:n asiakkaan X toiminnanohjausjär-
jestelmän matkalaskutoiminnallisuuksiin tarvittavat uudistustyöt sekä järjestelmässä
havaitut virheet, jotka tulisi uudistustöiden aikana korjata. Opinnäytetyön tarkoituksena
on tehdä toimeksiantajalle työsuunnitelma, jonka pohjalta uudistustyöt voidaan toteut-
taa.
8
2 OLIO-RELAATIOMALLINNUS
2.1 Relaatiomalli
Relaatiotietokanta on yleisin tietokantamalli vielä tänäkin päivänä, vaikka muitakin
malleja tiedon tallentamiseen on tarjolla. Relaatiotietokantojen tapa toimia ja tallentaa
tietoa nojaavat relaatiomalliin, jonka esitti ensimmäisenä Edgar F. Codd vuonna 1970.
(Connolly & Begg 2005: 70.)
Coddin esittämä malli pohjautuu predikaattilogiikkaan, jota en tässä käy läpi. Relaatio-
mallissa tiedot tallennetaan relaatioihin, joita relaatiotietokannoissa kutsutaan tauluiksi.
Tieto tallennetaan mallin mukaisesti monikkoina, jotka taas koostuvat attribuuteista.
Monikkoja kutsutaan tietokannassa riveiksi ja attribuutteja taas kutsutaan sarakkeiksi.
Kuvassa 1. esitetty taulukkoesitys on kaikkein tutuin esitystapa relaatiotietokannassa
oleville tiedoille. (Connolly & Begg 2005, 71–72.)
KUVA 1. Album relaatio ja sen osat.
Relaatiomallissa tiedot esitetään siis riveinä ja sarakkeina. Tietokannan tietyssä sarak-
keessa esitetyt tiedot edustavat aina samaa tietotyyppiä. Sarakkeen tietojen tulisi olla
atomisessa muodossa, toisin sanoen sarakkeen tiedot ovat skalaareja arvoja, eivätkä
täten sisällä arvonansa esimerkiksi toisia attribuutteja tai kokoelmia toisista attribuuteis-
ta. Kuvan 1. artist ja tittle sarakkeet ovat merkkijonotyyppistä tietoa, kun id taas on tie-
totyypiltänsä kokonaisluku.
Relaatiomallissa relaatiot voidaan yhdistää toisiinsa viittaamalla toisessa relaatiossa
esiintyvään uniikkiin arvoon, ns. avain-arvoon.
9
Taulukossa 1. näemme esimerkin tällaisesta tietojen yhdistämisestä. Vasemmalla puo-
lella on relaatio album ja oikealla puolella relaatio artist. Relaatiot yhdistetään toisiinsa
seuraavasti: Relaatiolla album on viittaavana tietona attribuutti artist_id.
Artist -relaatiolla taas on uniikisti relaation monikkoja identifioiva attribuutti id. Asiaa
tarkemmin katsottaessa voidaan havaita, että album -relaation artist_id 3 vastaa artist -
relaation id arvoa 3. Artist -relaation monikossa, jonka id on 3, on artisti nimeltä The
Cheesemakers. Täten album -relaation monikko, jonka id on 7, kuuluu artist relaatiosta
löytyvälle artistille nimeltä The Cheesemakers.
Tiedot esitetään relaatiomallissa edellä osoitetun kaltaisesti linkitettynä toisiinsa, mikä
ehkäisee tiedon moninkertaista tallentamista eli redundanssia. Esimerkkinä voitaisiin
ottaa kuvitteellinen tilanne, jossa pitäisi tallentaa kaikki The Cheesemakersin 256 levyä
tietokantaan. Ei olisi mitään järkeä tallentaa albumin tietoihin 256 kertaa tekstitietona
artistin nimeä, kun kerran on olemassa vain yksi tietty The Cheesemakers -niminen ar-
tisti. Parempi tapa on tallentaa album -relaatioon vain viittaus artist -relaation.
Taulukossa 1. kuvattu relaatioiden välinen yhteys on ns. yhden suhde moneen -yhteys,
joka on varmasti yleisin kaikista tietokannan taulujen välisistä yhteystyypeistä. Relaati-
oiden välillä voi olla myös yhden suhde yhteen tai monen suhde moneen -tyyppisiä yh-
teyksiä.
Yhden suhde yhteen -yhteys merkitsee nimensä mukaisesti tilannetta, jossa relaation
monikkoa vastaa toisessa relaatiossa yksi ja vain yksi monikko. Se ei siis saa esiintyä
toisessa taulussa kuin yhden kerran. Yleensä ottaen yhden suhde yhteen -yhteyksiin
törmää melko harvoin, yksi tyypillinen tilanne, jossa tämä yhteystyyppi tulee kyseeseen,
on ison relaation jakaminen pienemmiksi. Jakamalla iso relaatio pienempiin osiin saate-
taan saavuttaa jonkin asteisia suorituskykyparannuksia. Esimerkkinä tilanne, jossa re-
10
laatio sisältää attribuutteja, joita tarvitaan harvoin, mutta relaatioon tehdään paljon kyse-
lyjä muiden attribuuttien arvoihin. Tällaisessa tilanteessa voi olla suorituskyvyn kannal-
ta edullista jakaa relaatio pienempiin osiin: relaatiosta haetaan vain kiinnostavaa tietoa,
jos halutaan myös ne harvemmin tarvittavat tiedot mukaan, niin liitetään ne mukaan
vain tarvittaessa.
Monen suhde moneen -yhteys taas on huomattavasti paljon yleisempi yhteystyyppi.
Esimerkkinä voitaisiin käyttää tavaran tilausta. Asiakkaan pitää pystyä tekemään tilaus,
johon voi kuulua monta tuotetta. Relaatiot, joita tilauksen tekemiseksi tarvitaan, ovat
siis tilaus ja tuote. Näillä relaatioilla ei kuitenkaan pystytä toteuttamaan annettua vaati-
musta, jonka mukaan tilaukseen voi kuulua monta tuotetta. Tarvitaan uusi relaatio, jota
voitaisiin kutsua nimellä tilaustuote, johon attribuuteiksi tulevat tilaus -relaation yksi-
löivä tieto eli tilaus_id sekä tuote -relaation yksilöivä tieto, tuote_id. Näin saadulla re-
laatiolla voi olla esimerkiksi seuraavanlaiset arvot:
TAULUKKO 2. Monen suhde moneen -liitosrelaatio tilaustuote
Tilaus_id Tuote_id
1 2
2 1
2 2
Tilaukseen numero kaksi kuuluu siis useampia tuotteita. Rakenteellisella tasolla tieto-
kannassa ei ole olemassa suoraa monen suhde moneen -yhteyttä, vaan yhteys muodoste-
taan kahden yhden suhde moneen -yhteyden avulla, kuten kuvasta 2. ilmenee.
11
KUVA 2. Monen suhde moneen -yhteys ilmenee rakenteellisesti kahtena yhden suhde
moneen -yhteytenä.
Tietokannan rakenteellisten ominaisuuksien lisäksi relaatiomalli ottaa kantaa myös da-
tan eheyteen sekä siihen, millaisia operaatioita tiedon käsittelyyn sallitaan. Tiedon
eheyttä voidaan valvoa eheysrajoitteilla (integrity constraints), joista tärkeimpiä ovat
entiteetti eheysrajoite (pääavainrajoite), viite-eheysrajoite sekä arvojoukkorajoite. (Con-
nolly & Begg 2005, 82–83.)
Rajoitteisiin palaan myöhemmin Doctrine 2 annotaatioiden yhteydessä.
2.2 Oliomalli ja olio-ohjelmointi
Olio-ohjelmointi tarkoittaa ohjelmiston rakentamistapaa, joka perustuu ohjelmiston ku-
vaamiseen keskenään kommunikoivina olioina. Olioperustaisuus on yksi ohjelmointi-
malli muiden joukossa eli tapa kuvata järjestelmien rakennetta ja toimintaa. Muita mal-
leja ovat mm. proseduraalinen, funktionaalinen, logiikkaohjelmointi sekä rajoiteohjel-
mointi. Olioperustaisuus vastaa ihmisen tapaa jäsentää maailmaa ja siksi soveltuu muita
malleja paremmin kuvaamaan ohjelmien rakennetta: ihmisen on helpompi hahmottaa
ohjelman rakenne, kun sen käsitemaailma vastaa reaalimaailman asioita. (Koskimies
2000: 21.)
Oliomallin etuina muihin malleihin pidetään helpomman rakenteen jäsentämisen lisäksi
myös seuraavia seikkoja (Koskimies 2000, 23–25).
• Helpompi ylläpidettävyys
• Helpompi laajennettavuus
• Muutosten hallinta
• Koodin uudelleenkäytettävyys
12
Ylläpidettävyyttä ja laajennettavuutta helpottaa oliomallin tapa jakaa ohjelman rakenne
pienempiin, mahdollisimman toisistaan riippumattomiin osiin, jotka näin ollen ovat
mahdollisimman riippumattomia myös kokonaisuuteen kohdistuvista muutosvaatimuk-
sista, muutoksista tai virhekorjauksista: Muutos tai korjaus tarvitsee tehdä vain tiettyyn
kohtaan ohjelmaa, eikä koko järjestelmää tarvitse koodata uudelleen pienen muutoksen
takia. Pienten vaatimusmuutosten tulisi aiheuttaa vain pieniä koodimuutoksia. Tätä
ominaisuutta kutsutaan ohjelmiston jatkuvuudeksi. (Koskimies 2000, 24.)
Koska olio-ohjelmoinnissa ohjelma koostuu toisistaan mahdollisimman toisistaan riip-
pumattomista osista, voidaan toisinaan saada aikaiseksi myös uudelleenkäytettävää oh-
jelmakoodia. Uudelleenkäytettävyys ei ole mikään itsestäänselvyys, vaan vaatii syste-
maattista suunnittelua sen aikaansaamiseksi. Uudelleenkäytettävyyteen tähtäävä suun-
nittelu saattaa joskus olla jopa ristiriidassa sovelluksen käsiteanalyysiin pohjautuvan
suunnittelumenetelmän kanssa. (Koskimies 2000, 25.)
Olio-ohjelmointia ei myöskään voida pitää minään oikotienä onneen ohjelmistokehityk-
sen saralla, se ei esimerkiksi sinällään nopeuta kehitystyötä, pikemminkin päinvastoin:
Analyysi ja suunnitteluvaihe saattavat viedä enemmän aikaa kuin muita malleja käytet-
täessä. Pidempi kehitysaika maksaa kuitenkin usein itsensä takaisin ylläpidon ja laajen-
nettavuuden helppouden tarjoamilla ajallisilla ja taloudellisilla säästöillä. (Koskimies
2000, 25.)
Olio-ohjelmoinnin peruskäsitteitä ovat luokat ja oliot. Oliot ovat malleja reaalimaailman
asioista ohjelman sisällä. Vesterholm ja Kyppö määrittelevät olion seuraavasti: ”Olio on
jokin ympäristöstänsä erottuva kokonaisuus, jolla on oma identiteetti, sisäinen rakenne,
käytös ja viitteet ympäristöönsä. Luokka määrittelee olion rakenteen (attribuutit) ja
käyttäytymisen (metodit). Attribuutit ja metodit muodostavat yhdessä olion piirteet.”
(Vesterholm & Kyppö 2005, 79.)
Olioita ja luokkia mallinnettaessa käytetään yleisesti graafista UML-mallinnuskieltä.
Tämän esityksen puitteissa tulen pitäytymään UML -notaation luokka ja oliokaavion
piirteissä.
13
Olio-ohjelmoinnissa ohjelmakoodia kirjoitettaessa määritellään luokkia, joista ohjelman
ajon aikana syntyy tietokoneen muistiin olioita. Luokkaa voidaan siis ajatella muottina,
jolla voidaan tehdä tietynlaisia olioita. (Vesterholm & Kyppö 2006, 79.)
Aivan kuten relaatiomallissa relaatioilla, myös olioilla on attribuutteja. Attribuutit voi-
vat olla primitiivityyppisiä skalaareja arvoja, kuten levyn nimi, kappaleen järjestysnu-
mero jne. Erotuksena relaatiomallin relaatioihin, joiden attribuuttien arvot ovat lähtö-
kohtaisesti aina skalaareja arvoja, olion attribuutin arvot voivat olla ei skalaareja arvoja,
esimerkiksi toisia olioita tai vaikka taulukoita, jotka sisältävät toisia olioita.
Esimerkkinä oliosta, joka sisältää attribuuttinaan toisia olioita, voidaan käyttää oliota
auto (Kuva 3.). Autolla on reaalimaailmassa ominaisuuksia kuten moottori, renkaat jne.
Lähdettäessä rakentamaan ohjelmaa, joka simuloi reaalimaailman käsitettä auto, voi-
daan melko pian havaita, että kuten reaalimaailman vastineensa, myös ohjelma kannat-
taa rakentaa pienemmistä toisiinsa liittyvistä kokonaisuuksista. Ei ole järkevää kirjoittaa
koodia, jossa kaikki autoon liittyvä asia on yhdessä ja samassa paikassa. Mitä jos halu-
taan vaihtaa autoon erilaiset renkaat, pitääkö silloin tehdä muutoksia autoon? Onko jär-
kevää kirjoittaa koodia, jossa esimerkiksi moottori määritellään erittäin yksityiskohtai-
sesti suoraan auton ominaisuudeksi? Tällöin autoa kuvaavan ohjelmakoodin sisäinen
rakenne voi muodostua riippuvaiseksi moottoria koskevan koodin toiminnasta, jolloin
moottoria kuvaavaan koodiin kohdistuvat muutokset aiheuttavat muutoksia useaan koh-
taan autoa kuvaavaan koodiin.
Parempi tapa on tehdä uusi luokka nimeltä moottori, jonka sisällä määritellään mootto-
rin ominaisuudet, teho, käytetty polttoaine ja vaikkapa sylinterien lukumäärä. Moottori-
luokka voi metodiensa kautta tarjota auto -luokalle rajapinnan, jonka välityksellä auto
käyttää moottori -luokan ominaisuuksia. Näin luokkaan moottori tehtävät muutokset
eivät välttämättä aiheuta muutoksia auto -luokan toimintaan.
14
KUVA 3. Luokat Auto ja Moottori. Moottori -luokan vierellä on moottori -luokasta
luotuja luokan ilmentymiä eli olioita.
Jos kehityksessä käytetään jälkimmäistä tapaa, voidaan tehdä helposti useammanlaisia
auto -olioita, ilman, että tarvitsee muuttaa auto -luokan toteutusta joka kerta kun halu-
taan erilainen auto -olio. Kuvassa 4. on yksinkertaistettu esimerkki autosta ja siihen liit-
tyvistä olioista.
KUVA 4. Auto -oliolla on tietojäseninä muista luokista luotuja olioita.
Hyvin suunnitellut luokat voivat olla sellaisenaan käytettävissä myös irrallaan alkupe-
räisestä käyttökontekstistansa, esimerkkinä käytetty moottori -luokka voisi sellaisenaan
15
olla osana myös ohjelmassa, jossa tarvittaisiin moottoripyörän tai vaikka varavirta-
generaattorin moottoria. Tämä siis karkeana esimerkkinä uudelleenkäytettävästä luokas-
ta.
Oliosta, jonka tietojäsenenä on toisia olioita tai kokoelmia toisista olioista päästään kä-
siksi ongelmaan relaatiomallin ja oliomallin yhdistämisessä: Kuinka tallentaa ei skalaa-
reja arvoja (olioita) relaatiomallin relaatioon, joka lähtökohtaisesti hyväksyy attribuut-
tinsa arvoksi vain skalaareja, atomisia arvoja.
2.3 Olio-relaatiomappaus
Olio-relaatiomappaus on tekniikka, jota käytetään ohjelmointikielissä sovittamaan yh-
teen keskenään erilaiset ja yhtyeensopimattomat tietomallit. (Blanco, Borschel, Vesteri-
nen & Wage 2010, 13.)
Kuten kappaleissa 2.1 ja 2.2 kävi ilmi, relaatiotietokanta lähtökohtaisesti pitää kenttien-
sä tietosisältönä skalaareja arvoja, kuten kokonaislukuja, merkkijonoja jne. Oliot taas
voivat sisältää tietojäseninään myös muita olioita, jolloin oliota ei voi sellaisenaan tal-
lentaa relaatiotietokantaan, niin että sen tiedoista hakeminen olisi helppoa tai järkevää.
Tämä tilanne on havainnollistettu kuvassa 5.
KUVA 5. Oliota, jolla on tietojäsenenään toinen olio ei voida suoraan tallentaa relaatio-
tietokannan tauluun.
Olion tallentaminen tietokantaan on toki mahdollista esim. JSON -muodossa tekstikent-
tään, mutta tällöin hakeminen olion sisältävästä JSON tekstistä on useimmilla tietokan-
tatuotteilla erittäin hankalaa.
16
Jotkin tietokantatuotteet, kuten PostgreSQL ja MySQL tarjoavat uudemmissa versioissa
käyttöön myös JSON -tietotyypin, joka helpottaa olioiden tallentamista ja niiden sisäl-
löstä hakemista, mutta ominaisuuden tarjoamien mahdollisuuksien selvittely on rajattu
tämän työn ulkopuolelle.
Olioiden ja relaatiotietokannan relaatioiden välinen erilaisuus tulee siis ratkaista joko
jakamalla oliot yksiarvoisiksi joukoiksi tietokantaan tallentamista varten tai käyttää vain
yksiarvoisia arvoja ohjelmassa, mikä ei tietenkään ole järkevää.
Olio-relaatiomappausta sovelletaan ensin mainittuun lähestymistapaan. Ongelmana on
siis olioiden muuntaminen sellaiseen muotoon, että ne voidaan tallentaa tietokantaan ja
hakea tietokannasta niin, että olioiden ominaisuudet ja niiden väliset suhteet säilyvät
ennallaan. Jos olioita voidaan käsitellä näin, voidaan sanoa, että oliot ovat pysyviä (per-
sistent objects). (Blanco ym. 2010, 14)
Kun oliot on mapattu oikein relaatiotietokannan tauluihin, niin hakuja voidaan tehdä
järkevästi myös tietokannasta. Se, että kaikki tiedot ovat omissa kentissään, verrattuna
siihen, että tiedot on tallennettu JSON -muodossa tekstikenttään helpottaa oman koke-
mukseni mukaan kehittäjän ja ylläpitäjän työtä huomattavasti.
Olen omassa työssäni törmännyt tapauksiin, joissa ohjelmassa esiintyvän virheen ai-
heuttajan etsiminen tietokannasta on ollut jossain määrin uuvuttavaa. Tämä on johtunut
siitä, ettei tietoa voida suoraan hakea tietokannasta, vaan yhden tietokentän löytämiseksi
on tarvinnut kirjoittaa erikseen koodia, joka purkaa tiedon auki ihmiselle luettavaan
muotoon virheen etsimistä varten.
Olio-relaatiomappauksen tuloksena ohjelman suorituksen aikana tietokoneen muistiin
muodostuu virtuaalinen oliotietokanta, jota voidaan käyttää ohjelmointikielen avulla.
Virtuaalitietokannasta voidaan ohjelmointikieltä hyväksikäyttäen hakea tietoja, ja käsi-
tellä tietoja siten, että tiedot ovat oliomuodossa.
Olio-relaatiomapperi pitää huolen siitä, että tiedot menevät oikeisiin tauluihin ja kent-
tiin, kun tietoa tallennetaan fyysiseen tietokantaan sekä myös siitä, että tieto haetaan
oikeaan olioon fyysisestä tietokannasta tietoa haettaessa.
17
KUVA 6. ORM toimii virtuaalisena oliotietokantana sovelluksen ja tietokannan välissä.
18
3 Doctrine 2 ORM
3.1 Doctrine 2:n esittely
Doctrine 2 on avoimen lähdekoodin ORM ohjelmistokirjasto PHP-kielelle. Doctrine 2
tulee osana Symfony2 sovelluskehystä, mutta toimii myös itsenäisesti missä tahansa
PHP-projektissa. Symfony2:n lisäksi se integroituu erinomaisesti myös Zend Fra-
mework 2, CodeIgniter sekä Laravel sovelluskehysten kanssa. Doctrine 2 on asennetta-
vissa Composer pakettienhallinnan kautta. (Dunglas 2010, 14.)
Doctrine 2 mahdollistaa helpon tavan hakea tietokannasta tietoja suoraan olioksi sekä
tallentaa PHP -olioita tietokantaan ilman, että ohjelmoijan tarvitsee kirjoittaa riviäkään
SQL koodia.
Doctrine 2 pitää sisällään myös SQL:n kaltaisen kyselykielen, DQL:n (Doctrine Query
Language), jonka avulla voidaan kirjoittaa monimutkaisempia kyselyitä, jotka eivät ole
mahdollisia valmisfunktioiden avulla. Siinä on ominaisuutena myös työkalut tietokan-
taskeeman luomiseksi sekä PHP -luokkien luomiseksi valmiina olevasta tietokantas-
keemasta. (Dunglas 2013, 14.)
3.2 Doctrine 2:n perustoiminnot
Doctrine 2 ORM perustuu Data Mapper ja Unit Of Work suunnittelumalleihin:
(Dunglas 2013, 25.)
• Data Mapper on ohjelmistokerros joka erottaa muistinvaraiset oliot tietokannas-
ta. Sen tehtävänä on välittää tietoja näiden välillä sekä eristää ne toisistaan.
Muistinvaraisten olioiden ei tarvitse tietää edes sitä, että onko tietokanta olemas-
sa, ne eivät tarvitse SQL -koodia, eivätkä tietoa tietokannan skeemasta. (Fowler
2003)
• Unit Of Work -mallin avulla pidetään kirjaa olion tilaan suorituksen aikana tul-
leista muutoksista sekä siitä, mitä muutoksia tietokantaan pitää tehdä tehtyjen
operaatioiden jälkeen. Mallin avulla yritetään välttää lukuisia pieniä tietokanta-
kutsuja ja tehdä tarvittavat muutokset yhdellä kertaa. Mallin käyttämisen on tar-
19
koitus nopeuttaa tietokannan käyttöä sekä ehkäistä epäjohdonmukaista tiedonlu-
kua muistissa oleviin olioihin. (Fowler 2003)
Data Mapperin tehtävänä Doctrine 2:ssa on siis tehdä tarvittavat tietokantaan kohdistu-
vat tallennukset ja päivitykset niistä olioista, joita tietokantaan halutaan tallentaa. Data
Mapper myös huolehtii tiedon poistosta tietokannasta tarpeen mukaan eli silloin, kun
olio on merkitty poistettavaksi muistista. Näiden lisäksi Data Mapper huolehtii myös
olion ns. hydrate -operaatiosta eli tiedon hakemisesta tietokannasta muistinvaraiseen
olioon. (Dunglas 2013, 25.)
3.3 Doctrine 2:n EntityManager
Doctrine 2:ssa Data Mapperin tehtävää hoitaa EntityManager -niminen luokka. Nimensä
mukaisesti EntityManager hallitsee entiteeteiksi kutsuttuja olioita. Entiteetit ovat ns.
POPO olioita (Plain Old PHP Objects) eli aivan tavallisia PHP-kielellä määriteltyjen
luokkien instansseja. Entiteetit ovat ohjelmassa malleja tietokannassa esiintyvistä tau-
luista. Jokaista ohjelman ajamiseksi tarvittavaa tietokannan taulua varten Doctrine 2
tarvitsee entiteettiluokan.
EntityManager tarjoaa käytettäväksi seuraavanlaiset perustoiminnot:
• find($entityName, $id, $lockMode, $lockVersion)
o Ottaa parametrina entiteettiluokan nimen, jonka perusteella tietokannas-
ta haetaan tietoa sekä id:n, jolla tietoa haetaan. Palauttaa entiteettiluokan
tyyppisen olion. lockMode sekä lockVersion oletuksena arvoltaan null.
• persist($entity)
o Ottaa parametrina entiteetti instanssin, joka halutaan tallentaa tietokan-
taan. Olion tilaksi tulee persist funktiokutsun jälkeen ”managed”, joka
tarkoittaa sitä, että tieto tallennetaan tietokantaan kutsuttaessa Entity
Managerin flush funktiota tai kommitoitaessa transaktio.
• remove($entity)
o Ottaa parametrina entiteetti instanssin, joka halutaan poistaa tietokan-
nasta. Kuten persist funktio, myös remove ajetaan tietokantaan kutsutta-
essa flush funktiota tai kommitoitaessa transaktio.
• flush($entity = null)
20
o Oletuksena ajaa kaikki managed -tilassa oleviin olioihin kohdistuneet
muutokset tietokantaan eli synkronoi muistinvaraisen virtuaalisen olio-
tietokannan tiedot fyysisen tietokannan kanssa. Mikäli funktiolle anne-
taan parametrina entiteetti, vain tämä entiteetti ja siihen liittyvät mahdol-
liset poistojen sekä päivitysten vyörytykset synkronoidaan tietokantaan.
3.4 Doctrine 2:n konfigurointi
Doctrine 2 tarvitsee toimiakseen konfiguraatiotiedoston (koodiesimerkki 1), jossa mää-
ritetään tietokantayhteyden tarvitsemat tiedot, kuten tietokanta-ajuri, osoite sekä tieto-
kannan nimi. Tämän lisäksi tarvitaan myös bootstrap -tiedosto (koodiesimerkki 2), jolla
Doctrine ”käynnistetään”.
KOODIESIMERKKI 1. Doctrine 2 tietokantakonfiguraatio MySQL -tietokantaa varten.
Yhteydessä käytetään PDO -ajuria.
KOODIESIMERKKI 2. Doctrine 2 -käynnistystiedosto.
21
Koodiesimerkissä 2 konfiguroidaan Doctrine 2 käyttämään annotaatiopohjaista entiteet-
tien mappausta. Annotaatiot ovat kommentti/dokumentti -lohkoon sijoitettuja metatieto-
ja, joiden avulla Doctrine 2:lle kerrotaan, miten sen tulisi toimia. Muita mahdollisia
tapoja tehdä mappaukset on tehdä ne käyttäen XML -merkintää, YAML -merkintää tai
pelkästään PHP -koodia käyttäen.
Vaikka muita tapoja mappausten toteuttamiseksi on olemassa, niin esimerkeissä pitäy-
dyn kuitenkin annotaatiossa. Annotaatioiden eduiksi voidaan lukea parempi koodin lu-
ettavuus sekä helpompi ylläpidettävyys, johtuen siitä, että mappaustiedot ovat koodin
vieressä, eikä niitä tarvitse etsiä monesta paikkaa samanaikaisesti. (Dunglas 2013, 34.)
3.5 Doctrine 2:n entiteetit
Doctrine 2 entiteetit ovat pohjimmiltaan aivan tavallisia PHP -luokkia, jotka vastaavat
tietokannasta löytyviä tietokannan tauluja. Tekemällä tietyt määritykset, nämä luokat
ovat sellaisenaan tallennettavissa tietokantaan. Jos luokan oliolla on tietojäsenenään
toisia olioita, niin myös nämä haetaan ja tallennetaan tietokantaan automaattisesti.
Määrityksiä, joilla edellä mainitut operaatiot hoidetaan, kutsutaan mappaustiedoiksi.
Mappaustiedot taas merkitään käyttäen joko annotaatioita, XML -merkintätapaa,
YAML -merkintätapaa tai pelkkää PHP -merkintätapaa käyttäen. Koodiesimerkissä 3.
on esimerkki yksinkertaisesta entiteettiluokasta.
22
KOODIESIMERKKI 3. Yksinkertainen entiteettiluokka SiltaAccessType.
Koodiesimerkissä 3. on esitetty entiteetti, jolla on attribuutit id ja type. Kummankin
attribuutin yläpuolella on annotaatio, jossa määritellään attribuutin ominaisuuksia. Esi-
merkiksi muuttuja id on typpiä boolean, tyypiltänsä Id ja se on generoitu arvo eli jos
tietonkantana on MySQL, niin kentän arvo muodostetaan käyttäen AU-
TO_INCREMENT tyyppiä.
3.6 Doctrine 2:n annotaatiot
Annotaatiot ovat kaikessa yksinkertaisuudessaan metatietoja dokumenttilohkossa.
Doctrinessa käytetään annotaatioita kuvaamaan entiteetin attribuuttien ja luokkien omi-
naisuuksia, kuten tietotyyppi sekä mappaukseen ja tietokantaan liittyvien tietojen ku-
vaamiseen. Koodiesimerkissä 3 on esimerkki annotaatiosta.
23
KOODIESIMERKKI 3. Annotaatiossa määritetään attribuutin ominaisuudet sekä yh-
teydet muihin luokkiin.
Koodiesimerkissä 3. kuvattu annotaatio on osa SiltaAddress -entiteettiluokan määritte-
lyä. SiltaAddress luokalla on attribuuttina SiltaAccessLog -luokan olio. Tietokannan
puolella silta_address taulussa on vierasavain accesslog_id, jolla viitataan tauluun sil-
taAccesslog. Annotaation osa voidaan lukea selväkielisesti seuraavasti:
• Muuttuja accesslog on tyypiltänsä luokan SiltaAccesslog ilmentymä.
• Yhteystyyppi, jota annotaatiossa käytetään, on yhden suhde moneen -yhteys
• Kohde-entiteetti johon liitos tehdään, on SiltaAccesslog.
• Liitos tehdään tietokannassa siltaAddress -taulun attribuutin accessLog_id ja
taulun siltaAccessLog attribuuttiin id välille.
Annotaatioiden avulla voidaan määritellä myös erilaisia rajoitteita attribuuteille. Kes-
keisimmät rajoitteet, joita yleisesti määritellään, ovat arvojoukkorajoitteet (esim. merk-
kijonon maksimipituus, desimaalirajoittimet jne.), viite-eheysrajoitteet sekä pääavainra-
joite. Koodiesimerkissä 4 on esimerkki arvojoukkorajoitteesta, koodiesimerkissä 5 on
esimerkki viite-eheysrajoitteesta ja koodiesimerkissä 6 on esimerkki pääavainrajoittees-
ta.
24
KOODIESIMERKKI 4. Arvojoukkorajoite, jolla rajataan tuntipalkan kelvolliset arvot
välille 0.00 – 999.99.
Arvojoukkorajoite määritetään annotaation @ORM\Column -kohdassa asettamalla co-
lumnDefinition kohtaan DDL SQL pätkä, tässä tapauksessa SQL CHECK lause.
KOODIESIMERKKI 5. Monen suhde moneen yhteyden määrittely annotaatiossa.
Entiteettien välinen monen suhde moneen -yhteys määritellään koodiesimerkissä 5. ker-
tomalla annotaatiossa Doctrinelle, että kyseessä on monen suhde moneen -yhteys, pa-
rametrina annetaan kohde-entiteetti. Tämän jälkeen liitetään taulut, tässä tapauksessa
tilaaja, ja tilaajan puhelinnumerot eli yhteyden liitostaulu. Join columns -kohdassa ker-
rotaan minkä kenttien avulla liitos tehdään. inverseJoinColumns -kohdassa tehdään lii-
tos kohdetaulun ja liitostaulun välille eli liitetään liitostaulu kohdetauluun. Käytännössä
on siis muodostettu yhden suhde moneen -liitos taulujen tilaaja ja tilaajan puhelinnume-
rot sekä yhden suhde moneen -liitos taulujen puhelinnumero ja tilaajan puhelinnumerot
välille.
Koodiesimerkissä 5 kuvattu tapa tehdä liitoksia on eritäin kätevä siitä syystä, että näin
voidaan säästää melko tavalla aikaa ja turhaa työtä: Käyttämällä Doctrinen komentorivi-
työkaluja, on mahdollista luoda entiteeteistä tai malliluokista tietokantaskeema. Koo-
diesimerkissä kuvattu annotaatio luo komentorivityökalua käytettäessä myös liitostau-
lun automaattisesti eli sitä ei tarvitse erikseen luoda.
25
KOODIESIMERKKI 6. Pääavainrajoitteen määrittely.
Koodiesimerkissä 6 on määritelty pääavainkenttä id. Muuttuja on kokonaislukutyyppi-
nen, tarkkuus 0 (tarkkuutta ei käytetä kokonaisluvuilla), desimaaliskaala 0 (ei käytetä
kokonaisluvuilla), sen arvoksi ei voida asettaa NULL arvoa, ja se ei ole uniikkiavain.
@ORM\Id kertoo Doctrinelle, että tämä kenttä on pääavain. Tämän jälkeen vielä kerro-
taan Doctrinelle, että pääavaimen arvo on automaattisesti generoitu arvo sekä se, että
miten tämä arvo pitäisi muodostaa. Arvon muodostamiseen käytettävissä olevat strate-
giat ovat: (Doctrine Project Team, 74–75.)
• AUTO, oletusarvo, käyttää tietokantatuotteen oletustapaa arvon generoimiseksi,
täysin tietokantariippumaton (tuettujen järjestelmien puitteissa).
• SEQUENCE, käytetään tietokantaan luotua sekvenssiä arvon muodostamiseksi.
Sekvenssin käyttöä tukevat tuotteet: Oracle, PostgreSQL sekä SQL Anywhere.
• IDENTITY, käytetään tietokantatuotteen identity erikoiskenttää. Kentän toimin-
ta vaihtelee tietokantatuotteiden välillä, MySQL, SQLite ja SQL Anywhere
käyttävät AUTO_INCREMENT tapaa, MSSQL käyttää IDENTITY tyyppiä, ja
PostgreSQL tyyppiä SERIAL. Muille tuotteille ei ole tukea.
• UUID, arvon luomiseksi käytetään Doctrinen UUID generaattoria. Tietokanta-
tuoteriippumaton.
3.7 Doctrine 2:n Entity Repository
Ennemmin tai myöhemmin kehitystyössä tulee vastaan tilanne, jossa EntityManager
luokan tarjoamat perustoiminnot eivät enää riitä kyselyiden tekemiseksi kannasta, kyse-
lyissä voidaan tarvita esimerkiksi LEFT JOIN -tyyppisiä liitoksia, joita EntityManager
ei suoraan tarjoa.
Tällaisia tilanteita varten Doctrine 2 tarjoaa käytettäväksi EntityRepository luokasta
periytettävät repositoryluokat. Entiteetille voidaan annotaatiossa kertoa, mitä repository-
26
luokkaa tämän tulisi käyttää. Repositoryluokat on mahdollista generoida automaattises-
ti, josta lisää kappaleessa 3.9.2.
Repositoryluokkiin on mahdollista määritellä omia funktioita, joita voidaan tarpeen
mukaan hakea käytettäväksi. Funktioissa käytetään QueryBuilder -luokasta luotua oliota
kyselyiden rakentamiseen. QueryBuilder -luokan funktioilla on mahdollista luoda kyse-
lyitä koodiesimerkin 7. mukaisesti.
KOODIESIMERKKI 7. Kyselyn luominen QueryBuilderin avulla repositoryluokassa.
Koodiesimerkissä 7. haetaan admin -tyyppisiä käyttäjiä entiteetistä SiltaEmployee. Que-
rybuilderin metodien nimet muistuttavat hyvin paljon perinteistä SQL -syntaksia, jonka
takia sen käyttö on helppo omaksua.
Koodiesimerkissä 7. reporositoryluokkaan luotua omaa kyselyä voitaisiin kutsua käytet-
täväksi seuraavalla tavalla.
KOODIESIMERKKI 8. Repositoryluokassa sijaitsevan funktion kutsuminen.
3.8 Doctrine 2:n proxyluokat
Doctrine 2 käyttää oletuksena assosiaatioiden mappauksessa ns. lazy-loading -
tekniikkaa, joka tarkoittaa sitä, että kun ladataan tietokannasta olio, jolla on assosiaatio
johonkin toiseen olioon, niin tätä assosiaatiota ei ladata välittömästi tietokannasta, vaan
sitä edustaa Doctrinessa sitä varten tehty proxyluokka. ´
Proxyluokka perii aina entiteettiluokkansa ja toteuttaa \Doctrine\ORM\Proxy\Proxy
rajapinnan. (Doctrine Project Team, 36–37.) Proxyluokka voi täten laajentaa entiteetti-
27
luokan toimintaa, mutta pääasiallisesti proxyluokkia käytetään lazy-loading -tekniikan
toteuttamiseksi.
Proxyjen käytölle on pääasiallisesti kaksi syytä:
• Viittaus: Proxyn avulla voidaan viitata johonkin entiteettiin ilman, että sitä hae-
taan tietokannasta.
• Assosiaatio, kun Doctrinessa haetaan olioita, joilla on yksiarvoinen assosiaatio,
eikä kyselyssä tehdä join operaatiota, niin Doctrine luo proxy -olion muistiin, ja
tätä käytetään siihen saakka, kun olion tietoja oikeasti käytetään. Vasta siinä
vaiheessa ladataan tietokannasta olio muistiin.
Esimerkkinä viittausproxysta voitaisiin pitää tilannetta, jossa voidaan asettaa ostosko-
rioliolle tuoteolio, jos tiedetään tuotteen id. Tuotteen tietoja ei haeta tietokannasta muis-
tiin, ellei tuotetta ja sen attribuutteja käsitellä muutoin ohjelman ajon aikana, esim. haeta
tuotteen ominaisuuksia. Jos muita operaatioita tehdään, niin käytetään suoraan entiteet-
tiluokkaa ja tuotteen tiedot haetaan tietokannasta muistinvaraisiksi. Jos ei tuotetta käsi-
teltäisi millään muulla tapaa, säästettäisiin resursseja, koska tuotetta ei tarvitsisi hakea
tietokannasta muistiin ollenkaan.
Esimerkkinä assosiaatioproxystä voitaisiin ottaa työntekijäolio, jolla on assosiaatio
osastoon. Osastoja ei kuitenkaan haeta välittömästi tietokannasta, vaan osastoa varten
luodaan proxy, jossa on viittaustiedot osastoja varten. Jos ohjelman ajon aikana osastoa
koskevia tietoja haetaan, niin proxy häviää välistä ja käytetään suoraan osaston entiteet-
tiluokkaa.
Proxyluokan käyttö on tässä välissä täysin läpinäkyvää eli sen käyttöä tai käyttämättö-
myyttä ei huomata ajon aikana, eikä sen käyttö vaadi välttämättä ohjelmoijalta toimen-
piteitä.
Lazy-loading -tekniikkaan liittyy kuitenkin suorituskykyyn liittyviä ongelmia, jos asso-
siaatioiden määrät ovat suuria: Tietokannasta saatetaan hakea todella suuria määriä rive-
jä kerrallaan, joka hidastaa ohjelman toimintaa.
Proxyluokat voidaan generoida automaattisesti Doctrinen komentorivityökalun avulla,
tästä lisää kappaleessa 3.9.2.
28
3.9 Doctrine 2:n komentorivityökalut
Doctrine 2 tarjoaa kehittäjälle joukon erittäin käteviä komentorivityökaluja, joista mie-
lenkiintoisimmat ovat orm:generate, orm:convert, orm:schema-tool sekä orm:validate-
schema.
3.9.1 Orm:convert
Orm:convert työkalulla voidaan muuntaa mappaustietoja muodosta toiseen eli muuntaa
esimerkiksi XML -muodossa olevat mappaustiedot annotaatioiksi tai YAML -muotoon.
Orm:convert työkalulla on myös mahdollista takaisinmallintaa mappaustiedot jo ole-
massa olevasta tietokannasta.
Takaisinmallinnus on erittäin kätevä työkalu otettaessa Doctrine käyttöön jonkin sovel-
luksen jatkokehitysvaiheessa, sillä vaikkei se generoikaan täydellistä mappaustietoa,
niin ajansäästö suuremmissa projekteissa on huomattavaa. Esimerkiksi 36 taulua sisäl-
tävä työajanhallintaan tarkoitettu tietokanta generoitui annotaatioiksi alle kymmenessä
sekunnissa komentorivityökalun avulla. Annotaatioista generoitujen entiteettiluokkien
yhteenlaskettu koodirivien määrä tässä tapauksessa oli 5964 riviä.
Generoitu mappaustieto ei ole täydellistä, Doctrine projektin dokumentaatiossa maini-
taan, että takaisinmallinnusta käyttäen saavutetaan kuitenkin 70-80 prosenttisesti oikea,
tarvittava mappaustieto. (Doctrine Project Team 2016, 246.)
Hienosäätöä siis tarvitaan, mm. monen suhde moneen -yhteyksien sekä vyörytysmääri-
tyksien kohdalla, joita takaisinmallinnus ei mallinna oikein.
3.9.2 Orm:generate
Orm:generate työkalulla voidaan, kuten nimi antaa olettaa, generoida valitsimesta riip-
puen erilaisia asioita. Generate työkalulle on olemassa seuraavat valitsimet:
• Entities
• Proxies
• Repositories
29
Käyttämällä entities -valitsinta, voidaan generoida mappaustiedoista entiteettiluokat.
Mappaustiedot voivat olla annotaatioina, XML -muodossa tai YAML -muodossa. Valit-
simen optiolla –generate-annotations=”true” on mahdollista luoda generointivaiheessa
myös annotaatiot mukaan generoituihin entiteettiluokkiin. Tämä kannattaa mielestäni
tehdä, sillä se parantaa koodin luettavuutta ja helpottaa ylläpitoa.
Proxies -valitsimella on mahdollista generoida proxyluokkia entiteeteistä. Proxyluokkia
käytetään, jotta on mahdollista ladata muistiin osittaisia olioita tietokannasta. Tätä omi-
naisuutta kutsutaan nimellä lazy-loading.
Repositories -valitsinta käytettäessä Doctrine generoi entiteettien mukaiset Repository-
luokat. Kuten aiemmin mainittu, repositoryluokkia käytetään omien kyselyiden tekemi-
seen.
3.9.3 Orm:schema-tool
Orm:schema-tool -työkalua käytetään tietokantaskeemaan liittyvien operaatioiden suo-
rittamiseen. Schema-tool työkalulla on kolme valitsinta:
• Create, käytetään skeeman luomiseen. Mahdollista luoda mappaustietojen mu-
kainen skeema suoraan tietokantaan tai generoida SQL -koodi, joka voidaan ajaa
erikseen tietokantapalvelimella, mikäli skeemaan halutaan vielä tehdä muutok-
sia.
• Update, käytetään olemassa olevan skeeman päivittämiseen. Päivittää skeeman
suoraan tietokantapalvelimelle tai generoi muutoksia varten tarvittavan SQL -
koodin.
• Drop, toimii kuten SQL DROP -komento eli hävittää tietokannan.
3.9.4 Orm:schema-validate
Schema-validate työkalulla voidaan tarkistaa mappaustietojen oikeellisuus. Validointi-
työkalu ilmoittaa kaikista havaitsemistaan virheistä ja puutteista. Validointi on hyvä
tehdä komentorivityökalulla jo kehitysvaiheessa, koska Doctrinesta puuttuu joitakin
ajon aikaisia tarkistuksia, joten virheellisetkin mappaustiedot saattavat toimia, ainakin
siihen asti, kunnes virheeseen törmätään ajon aikana.
30
4 ORM:N HYÖDYT JA HAITAT
4.1 Hyödyt
Seuraavassa on lueteltu joitakin ORM tuotteen käytöstä havaitsemiani hyötyjä.
4.1.1 Ajan säästäminen ja turhan työn välttäminen
Orm -tuotetta käyttämällä voidaan saavuttaa kehitystyössä säästöjä työmäärää pienen-
tämällä. Rahallisten ja ajallisten säästöjen lisäksi voidaan myös ajatella, että työaika,
joka säästyy toistuvien CRUD (Create Read Update Delete) operaatioiden kirjoittami-
selta, voidaan sijoittaa laadukkaamman businesslogiikan kirjoittamiseen. (Block 2006.)
Koodiesimerkissä 9. havainnollistuu työajan säästö ja turhan työn välttäminen melko
hyvin.
KOODIESIMERKKI 9. Ajoneuvon tietojen tallentaminen tietokantaan, vasemmalla
perinteinen tapa ja oikealla puolella Doctrine 2 ORM.
31
Koodiesimerkissä 9. tehdään ajoneuvon ja ajoneuvon moottorin tallentaminen tietokan-
taan. Doctrine 2:lla tehty tallennus vie rivimääräisesti alle puolet siitä, mitä perinteiseen
tapaan tehty tallennus. Tässä on kyseessä erittäin yksinkertainen esimerkki. Mitä isom-
pia ja monimutkaisempia tallennuksia ja hakuja tehdään, sitä selvemmin ORM -tuote
tekee eroa perinteiseen tapaan.
4.1.2 Ylläpidettävyyden helpottuminen
Tietokantaan tehtävät muutokset heijastuvat koodiin vain mappaustietoihin, joten enää
ei ole tarvetta muuttaa lukuisia määriä kyselyitä. Oliot käyttävät automaattisesti muut-
tuneita mappaustietoja operaatioiden suorittamisessa.
4.1.3 Tietokantariippumattomuus
Sama koodi toimii useammilla tietokantatuotteilla, ei tarvetta kirjoittaa tuotespesifistä
koodia.
4.2 Haitat
Seuraavassa on listattuna joitakin itse havaitsemiani sekä yleisesti mainittuja ORM -
tuotteen käytöstä aiheutuvia kielteisiä seikkoja.
4.2.1 Liika abstraktio
ORM -tuotteet abstrahoivat tietokannan pois näkyvistä. Tämä on toisaalta haluttu omi-
naisuus, mutta saattaa myös hankaloittaa asioiden syvällisempää ymmärtämistä, ongel-
matilanteissa ei enää voida helposti nähdä mikä ohjelmassa on vialla jos ei se toimi.
Esimerkiksi Doctrine 2 heittää virhetilanteessa poikkeuksen, jonka perimmäisen syyn
selvittäminen voi hetkittäin olla työn ja tuskan takana. Perinteisellä tavalla tehty SQL -
kielinen kysely taas voitaisiin nopeasti testata kokeilemalla sitä suoraan tietokantaan ja
virheen syy paljastuisi melko nopeasti.
4.2.2 Virheen etsinnän vaikeutuminen
32
Virheen etsiminen ORM -tuotteella tehdystä koodista voi olla todella haastavaa, koska
debug -tietoa on tarjolla vähintäänkin riittävästi. Perinteisestä SQL kyselystä on yleensä
aika helppo sanoa, että mikä siinä on vialla. Toisin on ORM tuotteen kanssa.
Esimerkiksi Doctrine 2 saattaa tulostaa debug -tietoa minuuttitolkulla selaimen ikku-
naan, jos otetaan vähänkään summittaisempi lähestymistapa siihen, että mitä tietoja tu-
lostetaan. Toki tähän voidaan itse vaikuttaa, mutta perinteisiin var_dump() komentoihin
ei Doctrine 2:n kanssa kannata turvautua.
4.2.3 Suorituskykyyn liittyvät asiat
ORM -tuotteiden käyttöön saattaa liittyä suorituskykyongelmia. Yleisimmät suoritusky-
kyyn liittyvät ongelmat ovat:
• Suuri muistin käyttö
• Hitaus
Suuri muistin käyttö liittyy usein siihen, ettei tuotteen ominaisuuksia täysin käytetä hy-
väksi, esimerkkinä tilanne, jossa luetaan taulukkoon olioita silmukassa. Jokaisessa
haussa haetaan yksi olio, jolla on mapattuna assosiaatio toiseen olioon. Eli nyt tämänkin
olion tiedot haetaan ja suoritetaan vielä hydraatio-operaatio, joka myöskin on melko
raskas operaatio suorituskyvyn kannalta.
Lopulta on kasassa suunnaton määrä olioita, joita ei kaikkia välttämättä tarvita suorituk-
sen aikana. Näiden ylimääräisten tietojen lataaminen ja ylimääräisten hydraatio-
operaatioiden suorittaminen on usein estettävissä, pitää vain tietää miten se tehdään.
Hitaus voi liittyä samaan asiaan, em. esimerkissä saattaa muodostua tilanne, jossa tieto-
kantaa kuormitetaan kysely kerrallaan ja yhteys tukehtuu.
33
5 ZEND FRAMEWORK 2
5.1 Yleistä sovelluskehyksistä
Sovelluskehyksien käytölle on lukuisia syitä. Keskeisimmät syyt liittyvät seuraaviin
seikkoihin: (Abeysinghe 2009, 46–48.)
• Sovelluskehys ohjaa hyvien käytäntöjen (best practices) ja suunnittelumallien
(design patterns) noudattamiseen.
• Sovelluskehys helpottaa kehittämistyötä, koska se usein sisältää yleiskäyttöisiä
osia valmiina, kehittäjä voi näin ollen keskittyä juuri oman projektinsa kannalta
tärkeisiin asioihin.
• Yleiskäyttöiset osat ovat valmiiksi testattuja.
• Sovelluskehys tarjoaa rungon, jonka mukaan toimitaan, esimerkiksi Zend Fra-
mework 2 skeleton project, jossa on valmiina hakemistorakenne, joihin ohjel-
massa mukana olevat tiedostot tulisi sijoittaa.
• Sovelluskehykset poistavat kehitystyöstä turhaa monimutkaisuutta.
• Edellä mainituista syistä johtuen sovelluskehyksen käyttäminen säästää aikaa ja
rahaa
5.2 MVC-suunnittelumalli
MVC eli Model-View-Controller -suunnittelumallin tarkoituksena on ohjata kehitystyö-
tä siten, että käyttöliittymä, logiikka ja data ovat toisistaan eriytettyjä. (Abeysinghe
2009, 31)
Käytännössä tämä tarkoittaa sitä, että käyttöliittymäosaan (View) liittyvää koodia ei
pitäisi löytyä ohjelmalogiikan sisältävän ohjainosan (Controller) koodin seasta, eikä
kumpaankaan edellä mainittuun liittyvää koodia pitäisi löytyä malliosaan (Model) liit-
tyvän koodin seasta. Tätä eriyttämistä kutsutaan nimellä Separation Of Concerns (Haas-
teiden eriyttäminen). (Abeysinghe 2009, 33.)
Korkean tason käsitteenä MVC-suunnittelumalli tarkoittaa sitä, että ohjelman rakenne
jaetaan kolmeen osaan seuraavan kaltaisesti: (Freeman 2013, 51.)
34
• Model eli malliosa, jossa esitetään joko näkymällä tarvittava tieto, jota käsitel-
lään näkymien ja ohjaimen välillä tai vaihtoehtoisesti ns. domain-malli, jossa
esitetään sovelluksen käsittelemä tieto ja sen käsittelysäännöt.
• View eli näkymäosa. Näkymäosassa esitetään malliosan tiedot käyttäjälle
• Controller eli ohjainosa, jossa tehdään operaatioita näkymään ja malliin. Yleensä
tiedon käsittelyyn liittyvät osat, ns. businesslogiikka tulisi sijoittaa ohjainosan
koodiin.
Syitä sille, miksi MVC-mallissa eri osat erotetaan toisistaan, on monia. Ensinnäkin,
vaikka kaiken ohjelmaan liittyvän toiminnallisuuden ohjelmoiminen yhteen pakettiin
saattaa tuntua yksinkertaiselta ja alkuun ehkä säästää aikaakin, niin kokonaisuuden kas-
vaessa ohjelman eri osiin tulevien muutoksien hallinnasta muodostuu väkisinkin melko
haasteellista.
Eriyttämällä ohjelman osat toisistaan voidaan tehdä muutoksia eri osiin siten, että muu-
tokset yhteen osaan eivät parhaassa tapauksessa vaadi muutoksia muihin osiin ollenkaan
tai ainakin vaaditut muutokset ovat pieniä.
Toiseksi, eri osiin vaaditaan useammin muutoksia kuin toisiin. Esimerkiksi käyttöliit-
tymään tai businesslogiikkaan voi tulla useinkin muutoksia, kun taas datamalli usein
pysyy samanlaisena pitkiäkin aikoja.
Kolmanneksi, samaan datamalliin voidaan tehdä erilaisia näkymiä eli samaa mallia voi-
daan käyttää useammassa kohdassa ohjelmaa.
35
Malli
Näkymä Kontrolleri
Käyttäjä
ManipuloiPäivittää
Näyttää Käyttää
Manipuloi
KUVA 7. Kaaviokuva MVC -suunnittelumallin toimintaperiaatteesta
5.3 Zend Framework 2:n esittely
Zend Framework 2 (Tästä eteenpäin ZF2) on avoimen lähdekoodin (New BSD lisenssi)
sovelluskehys web-sovellusten ja palveluiden tekemiseen. Se on kehitetty täysin olio-
ohjelmoinnin periaattein ja tukee PHP versioita versiosta 5.3 eteenpäin. ZF2 on asennet-
tavissa Composer -riippuvuuksienhallinnan kautta. (Zend Framework 2016.)
ZF2:ssa tulee mukana mm. komponentit MVC -toteutuksia varten, tietokanta-
abstraktiokerros, lomake-, validointi- sekä autentikointikomponentit. ZF2:n komponent-
teja voi käyttää myös irrallaan sovelluskehyksestä, koska suunnittelussa on keskitytty
siihen, että komponenttien välillä olisi vain vähän tai ei ollenkaan keskinäisiä riippu-
vuuksia. Tehokkaimmillaan ne ovat kuitenkin yhdessä käytettyinä. (Zend Framework
2016.)
Composerin avulla voi asentaa ZF2 projektirungon, jonka päälle voi lähteä rakentamaan
omaa projektiansa. Kuvassa 8. on esitetty ZF2 projektin suositeltu oletusrakenne.
36
KUVA 8. ZF2 skeleton project, ZF2 projektin oletusrakenne.
5.4 Zend Framework 2:n moduuli
ZF2 sovelluskehityksessä perusyksikkönä toimivat module alihakemistosta löytyvät
sovellusmoduulit. Moduuli on siirrettävissä oleva, uudelleen käytettävissä oleva sekä
muihin moduuleihin liitettävissä oleva sovelluskomponentti. Moduuleja yhdistelemällä
on mahdollista rakentaa suurempia ja monimutkaisempia kokonaisuuksia. (Shasankar
2013, 27.)
ZF2 moduulien keskeisinä etuina mainitaan seuraavat asiat: (Shasankar 2013, 27.)
• Itsenäinen kokonaisuus, siirrettävissä oleva, uudelleenkäytettävissä
• Riippuvuuksien hallinta
• Kevyt ja nopea
• Tukee Phar -pakkauksia ja Pyrus -distribuutioita
ZF2 tarjoaa myös moduuleille valmiin perusrungon josta kehittäjä voi lähteä rakenta-
maan sovellusta. Skeleton moduulin saa kloonattua itselleen ajamalla seuraavan ko-
mennon projektin module alihakemistossa:
git clone git://github.com/zendframework//ZendSkeletonModule.git <appName >.
5.5 MVC -malli ja Zend Framework 2
37
ZF2 MVC -toteutus voidaan rakentaa kokonaan mukana tulevia komponentteja käyttäen
tai siihen voidaan ottaa mukaan ulkoisia komponentteja. FZ2 MVC -toteutuksen perus-
osat ovat:
• Controller: Periytetään jommasta kummasta luokasta:
o Zend\Mvc\Controller\AbstractActionController
o Zend\Mvc\Controller\AbstractRestfulController.
• Model: ZF2 tarjoaa malliluokkien käyttöön TableGateway luokan palvelut,
TableGateway voidaan kuitenkin korvata myös ORM tuotteella.
• View: ZF2 näkymiä luotaessa käytetään .phtml päätteisiä tiedostoja. Nämä tie-
dostot ovat käytännössä HTML koodia, jonka sekaan upotetaan PHP koodin
pätkiä.
• Forms: ZF2 formit ovat komponentteja, joiden avulla voidaan tehdä lomakkeita.
38
6 ASIAKKAAN ONGELMA
6.1 Asiakkaan X toiminnanohjausjärjestelmä
Evolvit Oy:n asiakkaalla X on käytössään toiminnanohjausjärjestelmä, joka on toteutet-
tu PHP ohjelmointikielellä. Toiminnanohjausjärjestelmä on siirtynyt Evolvit Oy:lle yl-
läpitoon ja jatkokehitykseen vuoden 2015 alussa. Tietokantana järjestelmässä käytetään
MySQL -tietokantaa. Toiminnanohjausjärjestelmää on kehitetty pitkään ja kehitystyön
saatossa siihen on luotu erittäin paljon ominaisuuksia. Toiminnanohjausjärjestelmän
perustehtävänä asiakkaan X käytössä on tukea liiketoimintaa tarjoamalla palvelut yri-
tyksen työntekijöiden asiakaskäyntien varaamiseen ja asiakaskäynneistä aiheutuneiden
palkka sekä kilometrikorvausten toimittamiseksi palkanmaksuun.
Asiakkaan X toiminnanohjausjärjestelmässä on ilmennyt kilometrilaskentaan liittyvä
vika, joka tulisi korjata. Samalla matkalaskujen sekä työntekijöiden menekinedistys-
työstä kirjattavien raporttien syöttölomakkeet tulisi uudistaa siten, että matkalaskuja
olisi mahdollista syöttää myös mobiililaitteilla.
6.2 Järjestelmän nykytila
Järjestelmän nykytilasta yleisesti voidaan todeta, että sen keskeiset ominaisuudet toimi-
vat varsin hyvin. Jotkin ominaisuudet taas ovat olemassa, mutta näyttävät keskeneräisil-
tä tai eivät toimi ollenkaan.
6.2.1 Ohjelmakoodi
Järjestelmää on kehitetty jo pitkän aikaa ja se näyttäytyy monenkirjavina ratkaisuina
toteutuksessa sekä koodin suurena määränä. Ohjelmakoodi on jaettu luokkiin, mutta
oikeastaan näitä luokkia käytetään vain nimiavaruuksina, käytännössä lähestulkoon
kaikki luokat kutsuvat vain omia funktioitansa. Joitakin poikkeuksia löytyy, mutta täl-
löinkin käytetään vain staattisia funktiokutsuja, olioita luokista ei juurikaan instantioida.
39
Näkymiä ja ohjelmalogiikkaa ei ole erotettu toisistaan, eikä käytössä ole myöskään mal-
liluokkia tietokantayhteyksiä varten. Ohjelmakoodi on näin ollen erittäin sekavaa ja
vaikeaa ylläpitää.
Nykytilassa järjestelmän näkymät on toteutettu siten, että eri toiminnoille on toteutettu
saman sisältöisiä näkymiä, vaikka toiminnallisuuksissa olisi vain vähäisiä eroavavai-
suuksia. Esimerkkinä voidaan ottaa vaikkapa matkalaskunäkymä: sama toiminto on
järjestelmässä toteutettu eri paikkoihin, ylläpitäjän matkalaskun korjausnäkymä, edusta-
jan matkalaskunäkymä, työntekijän matkalaskunäkymä jne.
Nämä kaikki näkymät ovat saman sisältöisiä, ne eroavat toisistaan lähinnä tallennuslo-
giikaltansa. Kaikki nämä näkymät olisi mahdollista toteuttaa siten, että käytettäisiin
samaa näkymää ja vain logiikka muuttuu käyttökontekstin mukaan. Tämä helpottaisi
ylläpidettävyyttä huomattavasti, koska kaikki muutokset näkymään tarvitsisi muutostöi-
den jälkeen tehdä vain yhteen paikkaan, eikä muutoksia varten tarvitsisi enää etsiä koo-
dia monesta eri paikasta.
6.2.2 Tietokanta
Järjestelmän taustalla oleva tietokanta sisältää 260 taulua. Tietokantamoottorina käyte-
tään MyISAM:ia muutamaa poikkeusta lukuun ottamatta. Tämä aiheuttaa sen, ettei tie-
tokantaoperaatioita voida ajaa transaktioissa, vaan ne suoritetaan operaatio kerrallaan,
eivätkä rollback -operaatiot ole mahdollisia.
Tietokannan taulujen välisiin yhteyksiin ei ole määritelty viite-eheysrajoitteita, yhteydet
hoidetaan vain viittaamalla toisen taulun pääavaimeen. Taulujen ja ”vierasavainten”
nimeämiskäytännöt ovat sekavia, eikä samoja nimiä ole käytetty yhtenäisesti eri pai-
koissa, mikä voi hetkittäin aiheuttaa hämmennystä tehtäessä liitoksia taulujen välille.
Taulujen väliset monen suhde moneen -yhteydet on joissain kohdin hoidettu ilman lii-
tostaulua, erottamalla viiteavaimet pilkulla toisistaan tekstikenttään. Järjestelmän toi-
minnan kannalta huolestuttavimpana kohtana voidaankin pitää juuri tietokannan kehnoa
suunnittelua. Usein tehtäessä järjestelmään uutta toimintoa joudutaan tekemään tieto-
kantaan muutoksia, jotka saattavat rikkoa jo olemassa olevia toimintoja.
40
6.3 Kilometrikorvausten laskennassa ilmennyt vika
Yrityksen työntekijöille maksetaan kilometrikorvauksia työpäivän aikana tapahtuneesta
matkustamisesta. Työntekijä täyttää työpäivän aikana tekemistään työsuoritteista rapor-
tin. Raportti kohdistuu aina jollekin myymälälle sekä projektille. Työntekijän on mah-
dollista tehdä myymälässä käydessään työsuoritteita useille projekteille.
Kilometrikorvauksia laskettaessa järjestelmän pitäisi nykytilassaan toimia seuraavalla
tavalla:
1. Työntekijä valitsee matkalaskut osion
2. Järjestelmä hakee tehdyt työpäivät ja ehdottaa työntekijälle mahdollisia päiviä,
joille matkalasku voitaisiin tehdä
3. Työntekijä valitsee mille päivälle matkalasku tehdään
4. Järjestelmä hakee kyseisen päivän käydyt myymälät ja esittää ne näkymässä
a. Ensimmäisenä ja viimeisenä paikkana näkyy työntekijän kotiosoite
5. Työntekijä voi vielä tässä vaiheessa lisätä ensimmäisen ja viimeisen paikan vä-
liin pysähdyksiä
6. Kun kaikki pysähdykset ovat näkymässä, työntekijä painaa ”laske kilometrit”
painiketta, joka hakee reitin kilometrit käyttäen Google Mapsin tarjoamaa raja-
pintaa
7. Käyttäjä painaa ”Tallenna matkalasku” painiketta
8. Matkalasku tallennetaan tietokantaan tilassa hyväksymätön
9. Esimies valitsee matkalaskun ja hyväksyy sen
10. Tallennus käynnistyy
a. Tässä vaiheessa haetaan käytyihin myymälöihin kohdistuvat projektit,
jotta matkalaskut voidaan jakaa projektin tilaajien kesken
b. Kilometrit jaetaan projektien kesken
c. Muodostetaan matkalaskukirjaus palkkoihin projekteittain
d. Tallennetaan palkkarivi tai rivit, jos projekteja oli useampia
Kilometrikorvausten tallennuksessa on kuitenkin vika, josta aiheutuu kilometrikorvaus-
ten kohdentuminen väärille projekteille. Vika johtuu siitä, ettei tietoja voida tietokannan
huonon suunnittelun takia liittää toisiinsa luonnollisesti, vaan kilometrien kohdentumi-
nen projekteille koitetaan kunnollisten viiteavainten ja liitostaulujen puuttuessa ikään
41
kuin arvata. Kuvassa 9. on esitetty kohdealue sellaisena kuin se esiintyy tietokannassa
tällä hetkellä.
KUVA 9. Matkalaskun laskentaan liittyvät keskeisimmät taulut.
Kuvasta 9. voidaan nähdä, että taulut mm_matkalasku ja mm_matkalasku_pysähdys
ovat ikään kuin irrallaan muusta raportointikokonaisuudesta. Matkalaskuun kuuluvan
pysähdyksen projektia yritetään hakea tietokannasta päivämäärän ja myymälän perus-
teella, mutta tämä ei anna luotettavia tuloksia.
6.4 Kilometrikorvauksien laskennan korjaaminen
Korjausten perustana tietokantaan tulee luoda yhteys taulujen projektit ja
mm_matkalasku_pysähdys välille. Kuvassa 10. on esitetty korjattu toimintamalli, jolla
tiedot saadaan liitettyä toisiinsa luotettavasti.
42
KUVA 10. Korjattu tietokantakuvaus: linkitystä varten on lisätty taulu monen suhde
moneen yhteyttä varten.
Uusi korjattu malli sallii järjestelmän toimia siten, että samalle pysähdykselle voidaan
kirjata useammalle projektille kuuluvia töitä. Samalla saadaan tietokantaan kirjattua
palkkakirjausten laskentaa varten varma tieto siitä, mille projekteille matkalaskun kilo-
metrit jakaantuvat.
Myös matkalaskun kirjaamisen logiikkaan tulee uuden tietokantamallin käyttöönoton
myötä muutos. Nykytilassa matkalasku kirjattaan järjestelmään vasta siinä vaiheessa,
kun työntekijä tekee matkalaskun. Korjausten jälkeen matkalasku kirjataan tietokantaan
heti ensimmäisestä työpäivän aikana tapahtuneesta pysähdyksestä. Työntekijälle voi siis
muodostua tietokantaan useita avonaisessa tilassa olevia matkalaskuja samanaikaisesti.
Halutessaan työntekijä voi käydä jättämässä matkalaskun hyväksyttäväksi esimiehelle
valitsemalla avoimista matkalaskuistansa matkalaskun, jonka haluaa hyväksyttäväksi.
Avoimena olevaa matkalaskua on mahdollista myös muokata eli matkalaskulle voidaan
lisätä tai poistaa pysähdyspaikkoja.
43
Kun matkalaskuihin kohdistuvat pysähdykset ja pysähdyksiin kohdistuvat projektit ovat
tietokannassa tallennettuna luotettavasti, matkalaskun laskennan toteuttaminen on lä-
hinnä triviaali toimenpide, jossa kerätään kilometrit projekteittain ja kirjataan palkkoi-
hin kulut projekteittain palkkariveiksi.
Matkalaskennan muutokset koskevat siis myös päivän aikana täytettäviä työraportteja
tai lähinnä niiden tallennuslogiikkaa. Näin ollen onkin järkevää tehdä näihin molempiin
osioihin kohdistuvat muutokset samaan aikaan.
Uudistustöissä käytettäviksi työkaluiksi valikoituivat Zend Framework 2 ja Doctrine 2
siitä syystä, että asiakkaan X järjestelmään on jo toteutettu näillä tekniikoilla toisenlai-
seen työhön kohdistuvien raporttien uudistaminen mobiiliystävällisemmiksi. Osaa jo
aiemmin tehdyistä Doctrine 2:n käyttämistä malliluokista voidaan uudelleen käyttää nyt
tehtävissä muutostöissä. Käyttöliittymien syöttölomakkeiden toteutuksessa tullaan käyt-
tämään Twitterin tarjoamaa Bootstrap -kirjastoa. Bootstrap antaa työkalut responsiivis-
ten käyttöliittymien tekemiseksi ja iso osa tyyleistä, joita uudistuksen aikana tullaan
tarvitsemaan, on jo valmiiksi määritelty aiemmassa uudistusprojektissa.
6.5 Muutostöiden toteuttaminen
6.5.1 Tietokanta ja entiteetit
Muutostöiden ensimmäisenä vaiheena korjataan tietokannan rakenne vastaamaan tal-
lennustarpeita. Samalla tietokannasta siivotaan pois rivejä, joiden viiteavaimet eivät
enää viittaa minnekään. Tyhjistä viittauksista aiheutuu ongelmia Doctrine 2:n kanssa,
jos koetetaan hakea tietokannasta rivejä, joita siellä ei enää ole, varsinkin, jos haettavat
rivimäärät ovat kovin suuria.
Kun tietokanta on saatu kuntoon, voidaan Doctrine 2:n komentorivityökaluilla tarkistaa
skeeman oikeellisuus ja luoda entiteettiluokat kohdealueen tauluista. Entiteettiluokat
tulevat tarvitsemaan jonkin verran käsityötä, jotta liitokset saadaan toimimaan oikein.
6.5.2 Tiedon validointi
44
Entiteettiluokkiin tullaan toteuttamaan myös syötetyn datan validointi. Tällä hetkellä
käyttäjän syötteitä ei lomakkeilla tarkasteta ollenkaan, vaan tiedot syötetään sellaise-
naan suoraan tietokantaan. Tämä malli mahdollistaa virheellisen tiedon syöttämisen,
mutta on myös tietoturvariski, koska sallii ns. SQL -injektoinnin.
Validointi tehdään siten, että entiteettluokat toteuttavat ZF2:n InputAwareInteface raja-
pinnan. Rajapinta vaatii toteutettavaksi kaksi funktiota, setInputFilter sekä getInputFil-
ter. Koodiesimerkissä 10. on kuvattu entiteettiluokassa toteutettu validointi, jossa käyte-
tään ZF2 InputFilteriä.
KOODIESIMERKKI 10. Attribuuttien validointi InputFilterin avulla.
Koodiesimerkissä 10. on esitetty yksinkertainen esimerkki InputFilterin toiminnasta.
Tässä esimerkissä luodaan ensin InputFactory, jonka avulla voidaan luoda InputFilter.
Tämän jälkeen tehdään taulukko, jossa on määritelty suodattimet, joilla syötteiden oi-
keellisuus tarkistetaan. Ensimmäinen suodatin tarkistaa seuraavat asiat kentälle id:
1. Että kentän arvo on syötetty, koska arvo vaaditaan esiintyväksi syötteissä
2. Syötteen tulee olla kokonaislukutyyppinen
45
3. Syötteen pienin sallittu arvo on 1
Toinen suodatin tarkastaa kentän kommentti oikeellisuuden:
1. Syötteestä karsitaan ensin pois html sisältö sekä turhat välilyönnit käyttäen
StripTags ja StringTrim suodattimia
2. Sen jälkeen tarkastetaan kentän pituus:
a. Syötteen tulee olla enkoodaukseltansa muotoa UTF-8
b. Syötteen maksimipituus on 2000 merkkiä, näin voidaan rajoittaa syötteen
pituus samaksi kuin mitä tietokannassa kentän sallittu maksimi on.
Kun suodattimet on määritetty, luodaan InputFilter -olio aiemmin luodun InputFactoryn
avulla. InputFactorylle annetaan parametrina suodattimet. InputFilter -olion luonnin
jälkeen voidaan palauttaa se kontrollerin käytettäväksi.
Validoinnin toteuttaminen entiteettiluokassa on myös siinä mielessä hyvä ratkaisu, että
kun se on kerran toteutettu, sitä voidaan käyttää uudelleen, mikäli entiteettiä käytetään
jossain toisessa yhteydessä.
Validointia tehdään kuitenkin myös kontrollerissa ja joissain tapauksissa on järkevää
tehdä sitä myös näkymässä. Näkymällä voidaan esimerkiksi ottaa välittömästi kiinni
käyttäjän ilmeisen virheellisiä syötteitä tai puuttuvia kenttiä, ennen kuin niitä lähetetään
eteenpäin käsiteltäväksi. Näin säästetään aikaa ja serverin kapasiteettia: sivua ei lähetetä
kontrollerin tai entiteetin tarkastettavaksi virheellisenä, koska jos virhe otettaisiin kiinni
vasta lähetyksen jälkeen, järjestelmän tulisi kuitenkin palauttaa käyttäjä virheellisenä
syötetylle lomakkeelle. Pahimmassa tapauksessa lomakkeen sisältöä pitäisi hakea uu-
delleen näkymälle, joka olisi totaalista resurssien haaskaamista.
6.5.3 Näkymät
Nykyiset näkymät tullaan korvaamaan ZF2:n näkymillä. FZ2:ssa näkymät muodoste-
taan siten, että kontrollerissa määritetään toimintoja (actions), jotka palauttavat näky-
män. Näkymänä käytetään ViewModel luokan ilmentymää.
Näkymälle voidaan antaa sitä kontrollerissa luotaessa muuttujia, joita voidaan tämän
jälkeen käyttää suoraan näkymällä. Itse näkymä, kuten aiemmin todettiin, tehdään erilli-
46
seen .phtml päätteiseen tiedostoon, johon voidaan kirjoittaa html koodin sekaan php
elementtejä. Muuttujana voidaan antaa myös kokonaisia käyttöliittymäkomponentteja,
kuten lomake eli Form.
Form on ZF2 komponentti, joka voidaan antaa näkymälle, jonka perusteella näkymällä
voidaan tehdä määrittelyjen perusteella lomake. Lomakkeen kentät voidaan määritellä
suoraan lomakkeen luovan luokan konstruktoriin tai ne voidaan antaa lomakkeelle eril-
lisinä Fieldset luokan ilmentyminä. Nyt tehtävissä muutostöissä kaikki lomakkeet tul-
laan tekemään niin pitkälle kuin mahdollista juuri Fieldsettien avulla. Tämän menetel-
män etuna on se, että näin voidaan määritellä lomakkeille valmiita kenttäjoukkoja, joita
voidaan tarpeen mukaan käyttää monessa paikassa. Esimerkkinä voidaan ottaa vaikkapa
matkalaskentaan liittyvät kentät. Kentät joita matkalaskentaan laskentaan tarvitaan,
ovat:
• Aloituspäivä
• Aloituskellonaika
• Ensimmäinen reittipiste
• Viimeinen reittipiste
• Painikkeet reittipisteiden lisäämiseksi ja poistamiseksi
• Matkan lopettamisvuorokausi
• Matkan lopettamiskellonaika
Tämän listan matkustusaikoja koskevista tiedoista on mahdollista tehdä Formille annet-
tava Fieldset. Fieldsetille on mahdollista määritellä kenttien tiedot. Koodiesimerkissä
11. on havainnollistettu fieldsetin toimintaa.
47
KOODIESIMERKKI 11. Fieldsetin käyttö.
Koodiesimerkissä 11. määritellään luokan konstruktorissa Fieldsetin sisältämät kentät.
Esimerkissä näkyvissä matkan aloitusajankohdan sisältävät kentät. Kentän nimi määrit-
tää näkymällä käytettävän html -tagin name -attribuutin arvon. Tyypillä määritetään
kentän tyyppi eli miten kenttä esitetään näkymällä. Optioina kummallekin määritellylle
kentälle annetaan otsikko, päivämääräkentälle päivämäärän esitystavan muoto, tuntiken-
tälle taas annetaan optiona vuorokauden tunnit. Attribuutteina annetaan päivämääräken-
tälle required eli vaadittu kenttä ja tuntikentälle attribuutti class, jolla voidaan jo tässä
vaiheessa määritellä kentän käyttämä css muotoilu.
Kun Fieldset on määritelty, sitä voidaan käyttää lomakkeella koodiesimerkin 12. tapaan.
KOODIESIMERKKI 12. Fieldsetin käyttäminen lomakkeella.
Koodiesimerkissä 12. havainnollistuu Fieldsetin käyttämisestä saatava hyöty: Samalla
lomakkeella käytetään kahdesti samaa Fieldsettiä eli säästytään ylimääräiseltä työltä.
48
Edellä luotu Form -komponentti Fieldsetteineen voidaan luoda kontrollerissa, josta se
voidaan antaa näkymäkomponentille. Näkymän tulostavalla .phtml tiedostolla lomake
voidaan tulostaa php:n echo komentoa käyttäen.
6.5.4 Kontrollerit
Muutostöiden toteutuksen tuloksena sovellukseen tehdään kaksi kontrolleria. Toinen
kontrollereista huolehtii menekinedistystyön raportoinnin tarpeista ja toinen matkalas-
kuraportoinnin tarpeista. Muutostöissä toteutettavat luokat tullaan periyttämään Abst-
ractActionController luokasta. AbstractActionController luokasta periytetyissä luokissa
toteutetaan actioneiksi kutsuttuja funktioita, jotka palauttavat näkymiä. Actioneissa to-
teutetaan suurin osa sovelluksen businesslogiikasta sekä valmistellaan näkymille annet-
tavat komponentit.
Nämä actionit täytyy lisätä moduulin konfiguraatiotiedostoon, jonka jälkeen actionin
toiminnallisuutta voidaan kutsua ohjaamalla liikenne konfiguroituun osoitteeseen, esi-
mekrkiksi matkalaskua voitaisiin hakea osoitteella http://mysite.com/matkalasku/123.
Edellä esitetty url palauttaisi matkalaskun jonka id kentän arvo on 123.
Käyttämällä edellä kuvatun kaltaista ohjausta, voidaan vanha ja uusi puoli sovelluksesta
yhdistää. Vanhalla puolella sovellusta on olemassa varsin käyttökelpoiset listanäkymät,
joista voidaan valita täytettäviä raportteja. Jatkossa sovellus vain ohjataan käyttämään
uutta raporttiominaisuutta vanhan sijaan vaihtamalla url -osoitteet.
Matkalaskutoiminnallisuuden toteuttavassa kontrollerissa tullaan käyttämään matkalas-
kentaan IvoryGoogleMapBundle -nimistä kirjastoa, jonka avulla voidaan laskea auto-
maattisesti työntekijän antamien reittipisteiden välinen matka käyttäen Google Mapsin
reittilaskentaa. Tätä kirjastoa on jo testattu ennestään, ja se tuntuu toimivan melko luo-
tettavasti.
49
7 JOHTOPÄÄTÖKSET JA POHDINTA
7.1 Doctrine 2
Doctrine 2 helpottaa omaa työtäni melkoisesti juuri tietokantaan kohdistuvien rutii-
nioperaatioiden vähentymisenä. Toisaalta alkuun tarvitsi panostaa paljon aikaa tuotteen
hahmottamiseen ja käytön opetteluun, sillä ominaisuuksia on todella paljon, pelkästään
manuaali kattaa 370+ sivua. Käytön opiskelua hankaloittaa myös se, ettei aiheesta ole
kirjoitettu paljon kirjoja. Nettilähteitä omatoimiselle opiskelulle löytyy toki runsaasti,
mutta tällöin muodostuu usein pulmaksi tiedon luotettavuus eli onko nettilähteessä neu-
vottu toimintamalli oikeasti parhaiden käytäntöjen mukainen.
Aivan pieniin projekteihin en Doctrine 2:ta lähtisi suosittelemaan työkaluksi, se tuntuisi
hieman yliampuvalta ratkaisulta, jos halutaan kehittää jotain pientä nopeasti. Suurem-
missa projekteissa sen käyttöönotto on mielestäni perusteltua sen takia, että tietokantaan
tulevia muutoksia tarvitsee periaatteessa mallintaa koodissa vain entiteettiluokkiin, jol-
loin säästytään suurelta määrältä työtä.
Otin työkoneellani auki olevasta projektista hieman statistiikkaa, projekti sisältää:
• 4719 INSERT lausetta
• 8507 UPDATE lausetta
• 6583 DELETE lausetta
• 23673 SELECT lausetta
Tällaisten kyselymäärien hallinta tietokantamuutosten kohdalla voi hetkittäin olla melko
haastavaa. Jokaista ei tietenkään tarvitse käydä läpi jonkun asian muuttuessa, mutta jon-
kin verran niitä täytyy aina käydä läpi muutosten ilmaantuessa.
ORM ei ole mikään oikotie onneen, se todellakin tuntuu hetkittäin hidastavan sovelluk-
sen toimintaa ja kuluttaa paljon muistiresursseja. Myös virheiden jäljittäminen voi han-
kaloitua ORM tuotteen käyttöönoton myötä.
50
ORM kuitenkin ohjaa kehitystä ja siinä käytettyjä käytäntöjä yhtenäisempään suuntaan
ja usein myös kannustaa ns. oikeiden toimintatapojen käyttöön. Jo pelkästään siitä syys-
tä olen valmis suosittelemaan sen käyttöä vähänkään isommissa projekteissa, joissa on
monta kehittäjää työskentelemässä samanaikaisesti. Omassa työssäni olen myös havain-
nut, että olemassa olevaan järjestelmään muutoksia tehtäessä jo varsin pienehkön omi-
naisuuden kehittämisen aikana tulevat tietokannan kaikkein keskeisimmät taulut jo mel-
kein väkisin mapatuksi Doctrine 2:ta varten. Näin ollen, kun kehitystyötä jatketaan,
saattaa olla jo paljonkin uudelleen käytettävää koodia olemassa.
7.2 Zend 2 Framework
ZF2 jakaa huomattavasti selkeämmin mielipiteitäni. Sen käyttöönotto projektissa ei ole
mitenkään kovin yksinkertaista ja sen dokumentaatio on paikoitellen melko työlästä
luettavaa. Projektin internetsivustolla on paljon erilaisia tutoriaaleja, mutta tutoriaaleissa
vaikuttaisi olevan melkoisesti virheitä, ainakin siitä päätellen, että käyttäjäkommenteis-
sa valitetaan, ettei koodi toimi, itse en näitä tutoriaaleja ole kovin paljon testaillut.
ZF2 vaatii valtavan määrän konfigurointia, ennen kuin varsinaisesti päästään tekemään
kehitystyötä. Konfiguraatiotiedostoja on myös ripoteltu ikävästi ympäriinsä eli asioita
täytyy pahimmillaan määrittää useisiin paikkoihin. Ajoittain ZF2 tuntuu käyttävän mel-
ko paljon muistia ja toimintojen suorittaminen tuntuu vievän kauan aikaa.
ZF2 on kuitenkin erittäin vakaan oloinen sovelluskehys, se tuntuu toimivan erittäin luo-
tettavasti. Se myös tarjoaa valtavan määrän toiminnallisuuksia sekä melkeinpä pakottaa
käyttämään hyviä toimintatapoja ja kun sen tapaan toimia pääsee sisälle, sen kanssa
työskentely on melko nopeaa.
Opinnäytetyön aiheena olleessa projektissa ZF2 tekee juuri sen mitä tarvitaankin: Sen
avulla voidaan erottaa sovelluksen eri osat MVC mallin mukaisiin osiin ja päästä eroon
moneen paikkaan tehdyistä saman ominaisuuden toteutuksista.
Itse en ZF2:ta suosittelisi aivan pieneen projektiin, sillä sen vaatima konfiguroinnin
määrä on pienen projektin tarpeisiin tarpeettoman suuri.
51
7.3 Pohdintaa
Olisi mielenkiintoista myös testata eri PHP sovelluskehyksiä rinnakkain, jotta voisi saa-
da realistisen kuvan niiden käytön helppoudesta tai vaikeudesta sekä niiden välisistä
suorituskykyeroista. Monista eri lähteistä lukemani perustaalla oletan, että ainakin La-
ravel sekä Symfony 2 olisivat voineet soveltua sovelluskehyksiksi tähän projektiin.
Kummatkin edellä mainituista vaikuttaisivat ainakin pintapuolisen tutustumisen perus-
teella helpommilta oppia kuin ZF2.
Useassa lähteessä mainitaan, että Laravelilla kehitystyö helpottuisi huomattavasti. Mel-
kein yhtä usein kuitenkin mainitaan, että suuremmissa projekteissa ZF2 olisi oikea va-
linta sen takia, että se pakottaa kehittäjät toimimaan tietyn mallin mukaisesti. Tämä on
erittäin kätevää varsinkin, jos projektissa on mukana useita henkilöitä: Kun kaikki toi-
mivat samalla tavalla, on kehittäjien helpompi ymmärtää toistensa kirjoittamaa koodia.
Opinnäytetyön kirjoittamisen aikana ymmärsin monia asioita käsittelyn kohteena olleis-
ta työkaluista, joita en ollut tullut aiemmin ajatelleeksi. Olen myös havainnut, että tä-
män työn kirjoittamisen aikana oppimani asiat ovat jo nyt helpottaneet työtäni muiden
ohjelmistokehysten ja ORM -tuotteiden opiskelussa.
Uskon myös vahvasti, että tämän työn kirjoittamisen aikana opitut asiat tulevat nopeut-
tamaan case esimerkissä käsitellyn tapauksen toteutustöitä merkittävästi.
52
LÄHTEET
Abeyshing, S. 2009. PHP Team Development. Birmingam, United Kingdom: Packt Publishing Ltd. Blanco, G., Borschel, R., Vesterinen, K. & Wage, J. 2010. Doctrine ORM for PHP: Guide to Doctrine 1.2. Version: manual-1.2-1_1-2010-05-05. Sensio Labs. Block, G. 2006. Ten advantages of an ORM (Object Relational Mapper). Luettu 28.3.2016 https://blogs.msdn.microsoft.com/gblock/2006/10/26/ten-advantages-of-an-orm-object-relational-mapper/ Connolly, T & Begg, C. 2005. Database Systems, Fourth Edition. Essex, United King-dom: Pearson Education Limited. Dunglas, K. 2013. Persistence in PHP with Doctrine ORM. Packt Publishing. Doctrine Project Team. 2016. Doctrine 2 ORM documentation. Luettu 28.3.2016 https://media.readthedocs.org/pdf/doctrine-orm/latest/doctrine-orm.pdf Fowler, M. 2003. A short summary of the patterns in Catalog of Patterns of Enterprise Application Architecture. Luettu 17.2.2016. http://martinfowler.com/eaaCatalog/dataMapper.html Fowler, M. 2003. A short summary of the patterns in Catalog of Patterns of Enterprise Application Architecture. Luettu 17.2.2016. http://martinfowler.com/eaaCatalog/unitOfWork.html Freeman, A. 2013, Pro ASP.NET MVC 5. New York, USA: Appress. Koskimies, K. 2000. Oliokirja, 2. muuttumaton painos. Helsinki: Satku – Kauppakaari. Vesterholm, M & Kyppö, J. 2006. Java ohjelmointi, 6. uudistettu painos. Helsinki: Tal-entum Media Oy. Shasankar V, K. 2013. Zend Framework 2.0 by Example Beginner’s Guide. Birming-ham, United Kingdom: Packt Publishing Ltd. Zend Framework 2016. About. Luettu 31.3.2016 http://framework.zend.com/about/