Kirjautuminen

Haku

Tehtävät

Oppaat: PHP-ohjelmointi: Osa 14 - Olio-ohjelmointi

  1. Osa 1 - Johdanto
  2. Osa 2 - Muuttujat
  3. Osa 3 - if-rakenne
  4. Osa 4 - for-silmukka
  5. Osa 5 - Taulukot
  6. Osa 6 - Lomakkeet
  7. Osa 7 - Nettisivusto
  8. Osa 8 - Lisää silmukoista
  9. Osa 9 - Tiedostot
  10. Osa 10 - Omat funktiot
  11. Osa 11 - Istunnot
  12. Osa 12 - Tietokannat
  13. Osa 13 - Tietoturva
  14. Osa 14 - Olio-ohjelmointi
  15. Osa 15 - Kuvien luonti
  16. Osa 16 - Säännölliset lausekkeet
  17. Osa 17 - Merkistöt
  18. Osa 18 - PHP:n ongelmat

Kirjoittaja: Antti Laaksonen (2011).

PHP:n tavalliset muuttujatyypit (lukuarvot, merkkijonot ja totuusarvot) sekä taulukot riittävät periaatteessa kaikkiin tarkoituksiin. Usein kuitenkin koodin rakenteen saa selkeämmäksi määrittelemällä omia erikoistuneita muuttujatyyppejä. Tämä onnistuu olio-ohjelmoinnin avulla.

Olio-ohjelmoinnin peruskäsitteet ovat luokka ja olio. Luokka määrittelee, millaista tietoa olio sisältää (luokan muuttujat) ja miten sitä voi käsitellä (luokan funktiot). Käytännössä luokkaa voi ajatella muuttujan tyyppinä ja oliota voi ajatella muuttujan arvona.

Tässä oppaassa tutustumme olio-ohjelmoinnin perusasioihin. Olio-ohjelmoinnin vaikeutena on, että se pääsee oikeuksiinsa vasta silloin, kun koodin määrä on suuri. Pienissä esimerkeissä oliot saattavat tämän vuoksi tuntua vain ylimääräiseltä lisäkuormalta. Vasta kokemus opettaa, mitä todellista hyötyä olio-ohjelmoinnista voi olla.

Kattava selostus PHP:n olio-ominaisuuksista on PHP:n manuaalissa.

Ensimmäiset oliot

Seuraava koodi määrittelee luokan Henkilo ja muodostaa siitä kaksi oliota:

<?php
class Henkilo {
    private $nimi;

    public function __construct($nimi) {
        $this->nimi = $nimi;
    }

    public function puhu() {
        echo "Hei, nimeni on {$this->nimi}! <br>";
    }
}

$aapeli = new Henkilo("Aapeli");
$maija = new Henkilo("Maija");

$aapeli->puhu();
$maija->puhu();
?>

Koodin tulostus on seuraava:

Hei, nimeni on Aapeli!
Hei, nimeni on Maija!

Luokan määrittelyn aloittaa sana class, jonka jälkeen tulee luokan nimi. Luokka muodostuu muuttujista ja funktioista, jotka määritellään luokan sisällä. Esimerkiksi luokassa Henkilo on muuttuja $nimi sekä funktiot __construct ja puhu. Luokan muuttujat ovat käytettävissä kaikissa funktioissa, ja niihin viitataan sanalla $this.

Muuttujan ja funktion määrittelyn edessä voi ilmoittaa sen näkyvyysalueen. Jos näkyvyysalue on private (yksityinen), muuttujaa tai funktiota voi käyttää vain luokan sisällä. Jos taas näkyvyysalue on public (julkinen), käyttäminen onnistuu myös luokan ulkopuolelta. Tavallisesti luokan muuttujat määritellään yksityisiksi, jolloin niitä pystyy käsittelemään luokan ulkopuolelta vain julkisten funktioiden kautta.

Funktio __construct on luokan konstruktori, jota kutsutaan aina, kun luokasta muodostetaan olio. Tavallisesti konstruktori asettaa alkuarvot luokan muuttujille. Esimerkiksi luokassa Henkilo konstruktorille annetaan henkilön nimi, joka sijoitetaan luokan muuttujaan $nimi.

Komento new luo luokkaa vastaavan olion. Luokan nimen jälkeen annetaan parametrit luokan konstruktorin mukaisesti. Yllä olevassa koodi luo luokasta Henkilo kaksi oliota ja tallentaa ne muuttujiin $aapeli ja $maija.

Esimerkki: Ostoskori

Tässä esimerkissä teemme ostoskorin, johon voi lisätä tuotteita. Esimerkki muodostuu kahdesta luokasta: Tuote vastaa yhtä tuotetta, ja Ostoskori sisältää joukon tuotteita.

Luokan Tuote sisältönä on tuotteen nimi ja hinta (Tuote.php):

<?php
class Tuote {
    private $nimi;
    private $hinta;

    public function __construct($nimi, $hinta) {
        $this->nimi = $nimi;
        $this->hinta = $hinta;
    }

    public function nimi() {
        return $this->nimi;
    }

    public function hinta() {
        return $this->hinta;
    }
}
?>

Olio-ohjelmoinnin käytännön mukaisesti muuttujat $nimi ja $hinta ovat yksityisiä. Tämän vuoksi luokassa on erilliset funktiot nimi ja hinta, joilla muuttujien arvot pystyy hakemaan luokan ulkopuolelta.

Luokka Ostoskori sisältää kaikki ostoskorin tuotteet (Ostoskori.php):

<?php
class Ostoskori {
    private $tuotteet = array();

    public function lisaa($tuote) {
        $this->tuotteet[] = $tuote;
    }

    public function maara() {
        return count($this->tuotteet);
    }

    public function lista() {
        $nimet = array();
        foreach($this->tuotteet as $tuote) {
            $nimet[] = $tuote->nimi();
        }
        return $nimet;
    }
}
?>

Luokan sisältönä on taulukko, jossa on ostoskorin tuotteet. Funktio lisaa lisää uuden tuotteen ostoskoriin, funktio maara palauttaa tuotteiden määrän ja funktio lista palauttaa kaikkien tuotteiden nimet taulukossa.

Seuraava koodi testaa luokkia:

<?php
include("Tuote.php");
include("Ostoskori.php");

$ostoskori = new Ostoskori();
$ostoskori->lisaa(new Tuote("selleri", 3));
$ostoskori->lisaa(new Tuote("retiisi", 2));
$ostoskori->lisaa(new Tuote("nauris", 5));
echo "Määrä: " . $ostoskori->maara() . "<br>";
echo "Tuotteet: " . implode(", ", $ostoskori->lista());
?>

Koodi tuottaa seuraavan sivun:

Määrä: 3
Tuotteet: selleri, retiisi, nauris

Perintä

Perintä on tavallinen olio-ohjelmoinnin tekniikka, jossa uuden luokan lähtökohtana on aiemman luokan sisältö. PHP:ssä perinnän ilmaisee sana extends luokan määrittelyn alussa.

Tässä on uudestaan aiempi Henkilo-luokka:

<?php
class Henkilo {
    private $nimi;

    public function __construct($nimi) {
        $this->nimi = $nimi;
    }

    public function puhu() {
        echo "Hei, nimeni on {$this->nimi}! <br>";
    }
}
?>

Seuraava luokka Ohjelmoija perii Henkilo-luokan:

<?php
include("Henkilo.php");

class Ohjelmoija extends Henkilo {
    private $kieli;

    public function __construct($nimi, $kieli) {
        parent::__construct($nimi);
        $this->kieli = $kieli;
    }

    public function puhu() {
        parent::puhu();
        echo "Ohjelmointikieleni on {$this->kieli}. <br>";
    }
}
?>

Luokka Ohjelmoija sisältää henkilön nimen lisäksi ohjelmoijan ohjelmointikielen. Ideana on, että luokan funktiot kutsuvat ensin ylemmän luokan vastaavia funktioita. Tämä onnistuu merkinnän parent avulla.

Seuraava koodi testaa luokkaa Ohjelmoija:

<?php
include("Ohjelmoija.php");

$ohjelmoija = new Ohjelmoija("Aapeli", "PHP");
$ohjelmoija->puhu();
?>

Koodin tulostus on seuraava:

Hei, nimeni on Aapeli!
Ohjelmointikieleni on PHP.

MVC-malli

MVC-malli on yleinen tapa nettisivuston rakenteen suunnitteluun. MVC-mallissa sivusto jakaantuu kolmeen osaan:

Tarkoituksena on, että malli huolehtii sivuston tietosisällöstä, näkymä luo käyttöliittymän ja ohjaimen vastuulla on mallin ja näkymän päivittäminen käyttäjän toimien perusteella. Malli ja näkymä ovat mahdollisimman erillään toisistaan, mikä selkeyttää järjestelmän rakennetta.

MVC-malli ei ole tiukka sääntökokoelma, vaan sen soveltaminen riippuu tilanteesta. Monet PHP:n sovelluskehykset, kuten Symfony ja Zend, hyödyntävät MVC-mallia. Seuraavaksi teemme itse yksinkertaisen MVC-malliin perustuvan sovelluksen.

Esimerkki: Chat

MVC-mallin esimerkkimme on chat, jossa käyttäjät voivat lähettää anonyymeja yksirivisiä viestejä nettisivulle. Voit kokeilla valmista chat-sovellusta tästä.

Luokka Malli vastaa chatin tietosisällöstä. Tässä tapauksessa tietosisältönä ovat chatin viestit, jotka tallennetaan tiedostoon viestit.txt. Luokka tarjoaa funktion viestit, joka palauttaa viestit taulukossa, sekä funktion lisaa, joka lisää uuden viestin.

<?php
class Malli {
    private $tiedosto = "viestit.txt";

    public function viestit() {
        if (file_exists($this->tiedosto)) {
            return file($this->tiedosto);
        } else {
            return array();
        }
    }

    public function lisaa($viesti) {
        $viesti = date("H:i:s: ") . $viesti;
        file_put_contents($this->tiedosto, "{$viesti}\n", FILE_APPEND);
    }
}
?>

Seuraava tiedosto nakyma.php sisältää chatin käyttöliittymän. Näkymä olettaa, että sitä kutsutaan luokassa, jonka taulukko $viestit sisältää chatissa olevat viestit. Käytännössä tämä luokka tulee olemaan ohjain, jonka teemme seuraavaksi.

<!DOCTYPE html>
<html>
  <head>
    <title>Chat</title>
  </head>
  <body>
    <h1>Chat</h1>
    <?php
    if (empty($this->viestit)) {
        echo "Chatissa ei ole viestejä.";
    } else {
        foreach ($this->viestit as $viesti) {
            echo htmlspecialchars($viesti) . "<br>";
        }
    }
    ?>
    <h2>Uusi viesti</h2>
    <form action="?toiminto=lahetys" method="post">
      <input type="text" name="viesti">
      <input type="submit" value="Lähetä">
    </form>
  </body>
</html>

Luokka Ohjain sisältää kaksi funktiota: lista hakee chatin viestit mallista ja näyttää ne näkymässä, ja lahetys lähettää uuden viestin malliin ja ohjaa käyttäjän takaisin listaan.

<?php
class Ohjain {
    private $malli;

    public function __construct() {
        $this->malli = new Malli();
    }

    public function lista() {
        $this->viestit = $this->malli->viestit();
        include("nakyma.php");
    }

    public function lahetys() {
        $this->malli->lisaa($_POST["viesti"]);
        header("Location: chat.php?toiminto=lista");
    }
}
?>

Kaikki edellä luodut osat kytkee yhteen sivu chat.php:

<?php
include("Malli.php");
include("Ohjain.php");

$ohjain = new Ohjain();
$toiminto = $_GET["toiminto"] ?? null;
if (!in_array($toiminto, ["lista", "lahetys"])) {
    header("Location: chat.php?toiminto=lista");
} else {
    $ohjain->$toiminto();
}
?>

Tässä sivulle annettu parametri toiminto kertoo, mistä sovelluksen osasta on kysymys. Chatissa toiminto on käytännössä joko "lista" tai "lahetys". Huomaa muuttujan $toiminto käyttäminen olion $ohjain funktion valinnassa.

Tässä tapauksessa MVC-mallin käyttäminen on siinä mielessä turhaa, että vastaavan sovelluksen voisi toteuttaa ongelmitta yksinkertaisemmin ilman MVC-mallia. Esimerkin lähestymistapaa voisi kuitenkin soveltaa laajemmassa järjestelmässä, jossa MVC-mallin todelliset edut tulisivat esille. Tyypillisesti malli, näkymä ja ohjain muodostuvat suuresta määrästä tiedostoja.


Kommentit

Pentu [30.06.2012 17:19:05]

#

Jotain tällaista kaipasinkin! Pitkälle olen päässytkin ilman luokkia, mutta nyt ois jo varmaan aika opiskella niitäkin :P kertalaakista ei vielä tämä homma uponnut, mutta eiköhän tuo harjoittelemalla ja kokeilemalla ala luonnistua!

latenleffahylly [01.08.2012 07:49:08]

#

Erittäin vaikeaa. Pidän olioista, mutta ne saattavat myös sekoittaa pakkaa.. eli pitää selvittää milloin niitä kannattaa hyödyntää, kiitos oppaasta.

vesikuusi [21.02.2013 13:36:14]

#

Hyvä opas. Itselle oli uutta tuo MVC. Ihmettelin vain sitä, kun Ohjain::lahetys() kutsuu headeria, vaikka sivulla on jo sisältöä...?

Metabolix [21.02.2013 13:39:20]

#

vesikuusi, eihän sivulla ole sisältöä silloin. Kun lomake lähetetään, tapahtuu uusi sivunlataus, jolla ei tulosteta minkäänlaista sivua vaan suoritetaan ainoastaan tuo lahetys-metodi. Seuraa koodia rivi kerrallaan, niin ymmärrät.

vesikuusi [30.03.2013 14:25:58]

#

Moi olinkin unohtanut tämän kokonaan :D Joo kiitti kun avasit asiaa, Metabolix. Jotenkin oli mennyt ohi tuo formin action varmaan, tai koko formi jos on nukkunut huonosti :p

Tuubaaja [12.09.2017 12:54:06]

#

Onneksi on tämä ohjelmointiputka. Täällä on oikein hyvää informaatiota. Tällainen sivistävä asiakokonaisuus pitäisi kaikkien oppia, ainakin perusteet. Kiitos

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta