Guide: PHP- & MySQL-innføring: Kapittel 7

Objekt-orientering

Etter en lengre juleferie skal vi dekke objekt-orientering, som er en metode for å sette data og oppførsel i sammenheng.

Side 1: Introduksjon

Vi er nå klar med det syvende kapittelet i vår gjennomgang av PHP og MySQL. Vi har ennå ikke kommet frem til den delen som skal handle om MySQL, men vi nærmer oss med stormskritt. I dag er temaet hvordan man kan lage objekter, som lar deg samordne data med en gitt type oppførsel.

Her er de tidligere kapitlene i vår gjennomgang;

Forord

I dette kapittelet har vi valgt å benytte oss av det nye objekt-orienterte systemet som ble innført med PHP5. PHP5 har vært tilgjengelig siden juli 2004, og likevel er det svært mange webhotell som enda ikke har tatt det i bruk. Det er likevel tilgjengelig i blant annet XAMPP-pakken, som vi anbefalte i det første kapittelet av denne artikkelserien.

Det er såpass mange endringer mellom PHP4 og PHP5 når det kommer til objekt-orientert programmering, at dersom du ikke har tilgang til en PHP5-basert installasjon, vil dette kapittelet dessverre være stort sett unyttig for deg når det gjelder programmeringsbiten.

Likevel presenterer vi også mye teori i dette kapittelet, og den vil ikke være helt bortkastet, selv om noe av det ikke er mulig å bruke i PHP4.

Side 2: Klasser og objekter

Klasser og objekter

Hva er så objekt-orientering? Når man programmerer jobber man i all hovedsak med to ting; data og oppførsel. Til nå har vi kalt dataene våre vært lagt i variabler, mens oppførselen finner vi i funksjoner, og de har dermed vært separate. Objekt-orientering lar deg gruppere data og oppførsel i klasser.

Klasser og objekter er de to hovedbegrepene man må innom når man snakker om objekt-orientert programmering. En klasse beskriver hvordan et objekt ser ut, hvilke funksjoner og variable det har. En klasse kan på den måten sammenlignes med konstruksjonstegningen over et hus; den beskriver hvordan man skal bygge huset og hva det skal inneholde, men konstruksjonstegningen er ikke selve huset. Du kan bygge flere hus (objekter) fra en tegning (klasse).

Objekt-orientering er på denne måten en enkel måte å definere hvordan data og oppførsel henger sammen. Tenk deg en klasse som heter Hund, som sier at objekter laget fra denne klassen har fire bein, en hale, et navn, og metodene lagLyd() og logreMedHale(). Klassen definerer da bare det grunnleggende om hvordan en hund oppfører seg, men er nettopp bare det, en abstrakt beskrivelse.

Vi kan definere vår abstrakte hund ved hjelp av PHP-nøkkelordet class. Her er hvordan undertegnede ser for seg en hund;


<?php
class Hund
{
    
// standardutstyr på en hund
    
private $bein 4;
    private 
$hale TRUE;
    
// navnet er individuelt
    
private $navn;
    
    
// lag en ny hund med et gitt navn
    
public function __construct($navn)
    {
        
$this->navn $navn;
    }
    
    
// lager en typisk hundelyd
    
public function lagLyd()
    {
        echo 
$this->navn." sier: Voff!<br/>";
    }
    
    
// lar hunden vår logre med halen
    
public function logreMedHale()
    {
        echo 
$this->navn." logrer med halen!<br/>";
    }
}
?>

Merk at om du kjører dette PHP-skriptet, vil du ikke få noe utdata i det hele tatt. Det er fordi dette bare er en definisjon, og vi oppretter på ingen tidspunkt et Hund-objekt, men vi sier bare noe om hvordan et Hund-objekt ville sett ut dersom vi hadde produsert en Hund.

class-nøkkelordet etterfølges av navnet på klassen vi definerer, og alt som er innenfor de påfølgende klammeparentesene er en del av klassedefinisjonen. Medlemsvariabler, slik som $bein, $hale og $navn i eksempelet, må skrives med et spesielt nøkkelord som angir synlighet foran. Her har alle medlemsvariablene synligheten private, noe vi skal komme nærmere inn på senere.

Funksjoner deklareres på samme måte som tidligere, bare med unntaket at du må ha private, protected eller public foran nøkkelordet function. En av funksjonene i eksempelet over er spesiell, __construct(). Denne kalles en konstruktor, og er den som blir kjørt når du instansierer (lager) et Hund-objekt. For å lage et Hund-objekt, må du gi hunden ditt et navn. $this->navn gir deg tilgang til hundens interne navn-variabel, mens $navn kun peker til parameteret til funksjonen __construct().

$this er en spesiell variabel som gir deg det objektet som du jobber på. Det er gjennom denne variabelen du får tilgang til funksjonalitet som finnes andre plasser i objektet (om det måtte være variabler eller funksjoner).

Lage objekter

Å opprette objekter når man har klassedefinisjonen er svært enkelt. Om du skriver følgende kode nederst i skriptet ovenfor, vil du få skrevet ut to linjer når du kjører det.



$fido = new Hund("Fido");
$fido->lagLyd();
$fido->logreMedHale();

Det er den første linjen som oppretter objektet, og du er nødt til å gi inn det antall parametre som konstruktoren krever. Nøkkelordet er new, og du må huske å tilordne objektet ditt en variabel.

De to neste linjene kaller funksjoner på objektet, først lagLyd() og deretter logreMedHale(). Legg merke til syntaksen som blir brukt her, $fido->lagLyd() sier at i objektet som $fido inneholder, kall metoden lagLyd(). Om du har en medlemsvariabel med synlighet public, kan du få tilgang til den med $fido->variabel, den eneste forskjellen er de manglende parentesene.

Side 3: Arv

Arv

Arv betyr noe annet i den objekt-orienterte verden enn det gjør i virkeligheten; her er det ingen dødsfall involvert. Når vi snakker om arv i OOP, snakker vi i en mer evolusjonsbasert måte. En Hund-klasse kan gjerne arve fra klassen Dyr. En Rottweiler-klasse vil arve fra klassen Hund.

Dette gjør det mulig å lage enkle hierarkier, hvor man kan endre på små ting ved oppførselen. For eksempel ville jeg utvidet det forrige eksempelet på denne måten;


<?php
class Hund
{
    
// standardutstyr på en hund
    
protected $bein 4;
    protected 
$hale TRUE;
    
// navnet er individuelt
    
protected $navn;
    
    
// lag en ny hund med et gitt navn
    
public function __construct($navn)
    {
        
$this->navn $navn;
    }
    
    
// lager en typisk hundelyd
    
public function lagLyd()
    {
        echo 
$this->navn." sier: Voff!<br/>";
    }
    
    
// lar hunden vår logre med halen
    
public function logreMedHale()
    {
        echo 
$this->navn." logrer med halen!<br/>";
    }
}

class 
Rottweiler extends Hund
{
    protected 
$hale FALSE;

    
// overskriver funksjonen i Hund
    
public function lagLyd()
    {
        echo 
$this->navn." sier: Grr!<br/>";
    }

    public function 
logreMedHale()
    {
        echo 
$this->navn." prøver å logre med ".
            
"halen, men oppdager at rottweilere ".
            
"ikke har hale!<br/>";
    }
}

$fido = new Rottweiler("Fido");
$fido->lagLyd();
$fido->logreMedHale();
?>

Det viktige i eksempelet ovenfor er nøkkelordet extends i definisjonen av Rottweiler. Det sier at vi arver alle egenskapene til Hund-objektet. Det er i PHP bare mulig å arve fra en foreldreklasse. Vi definerer så noen av tingene på nytt, slik som $hale, som vi setter til usann som standard på en Rottweiler. Vi definerer også metodene lagLyd() og logreMedHale() på nytt, fordi vi ønsker at disse skal ha forskjellig oppførsel når vi jobber med et Rottweiler-objekt.

En Rottweiler har altså alle de samme egenskapene og metodene som en Hund, men oppførselen kan være annerledes. Vi hadde ikke trengt å definere lagLyd()-metoden i Rottweiler-klassen, men vi kunne likevel brukt metoden uten å tenke på hvor den er definert. I så tilfelle ville den pekt tilbake på lagLyd()-metoden i Hund-klassen.

I dette tilfellet kaller vi Hund for grunnklassen (eng. "base class"), og Rottweiler for subklassen (eng. "subclass"). Hund blir også omtalt som foreldreklassen til Rottweiler, og vi sier at Rottweiler arver fra Hund.

Side 4: Synlighet

Synlighet

I eksemplene til nå har vi sett flere nøkkelord som vi ikke har forklart, blant annet protected og public. I tillegg finnes et nøkkelord private som kan plasseres på samme plass. Disse nøkkelordene angir synligheten til en egenskap eller en metode. Du kan tenke på synlighet som en form for tilgangskontroll, dine foreldres hus og alle dets egenskaper er f.eks. (sannsynligvis) protected, som betyr at du (en subklasse av dine foreldre) har tilgang.

Den mest restriktive formen for synlighet er private. Private metoder og variabler kan bare benyttes av funksjoner som tilhører klassen, men ikke av subklasser. Her er et eksempel;


<?php
class Forelder
{
    private 
$variabel 2;
    
    public function 
ForeldreMetode()
    {
        echo 
'Variabel er '.$this->variabel.
            
" (fra Forelder::ForeldreMetode)\n";
    }
}
class 
Barn extends Forelder
{
    public function 
BarnMetode()
    {
        
// dette feiler!
        
echo 'Variabel er '.$this->variabel.
            
" (fra Barn::BarnMetode)\n";
    }
}
$forelder = new Forelder();
$barn = new Barn();

$forelder->ForeldreMetode(); // OK
$barn->ForeldreMetode(); // OK
$barn->BarnMetode(); // ingen advarsel, men feiler
// disse to linjene blir aldri kjørt, 
// PHP gir feil
echo 'Variabel er '.$forelder->variabel.
    
' (på $forelder)'."\n";
echo 
'Variabel er '.$barn->variabel.
    
' (på $barn)'."\n";
?>

De to første kallene, som kaller ForeldreMetode() vil være vellykkede, men BarnMetode() har ikke tilgang på $this->variabel fordi denne er private, og vil derfor bare skrive ut "Variabel er ". Når vi prøver å få tilgang til variabelen fra helt utenfor objektet, får vi en fatal feil fra PHP, og skriptet slutter å kjøre. Her er resultatet av å kjøre dette skriptet.


Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er (fra Barn::BarnMetode)

Fatal error: Cannot access private property Forelder::$variabel in /var/www/tests/php-guide/7/5.php on line 29

Om vi endrer $variabel til å være protected i eksempelet ovenfor, vil Barn få tilgang til $variabel, men den vil fremdeles være utilgjengelig fra utsiden av klassen. Med denne lille endringen får vi følgende utdata:


Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er 2 (fra Barn::BarnMetode)

Fatal error: Cannot access protected property Forelder::$variabel in /var/www/tests/php-guide/7/5.php on line 29

Som du kan se fikk vi nå tilgang til $variabel fra BarnMetode(), men vi kan likevel ikke hente verdien uten fra objektet. For å få til det må vi gjøre variabelen public, og om vi gjør endringen blir resultatet slik;


Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er 2 (fra Forelder::ForeldreMetode)
Variabel er 2 (fra Barn::BarnMetode)
Variabel er 2 (på $forelder)
Variabel er 2 (på $barn)

Side 5: Statiske medlemmer

Statiske metoder og variable

En statisk metode eller variabel, er noe som er felles for alle instanser av en klasse. I stedet for å kalle en metode på et faktisk objekt, kan en statisk metode kalles direkte på klassen. En statisk variabel er lik for alle instansene av en klasse, og om du setter den i et objekt, så vil alle objektene observere den nye verdien.

Selve muligheten for å gjøre noe statisk kan virke noe forvirrende i begynnelsen, fordi hele poenget med objekt-orientering var å gruppere data og oppførsel, men ved å lage metoder som jobber på klassen og ikke på enkeltobjekter forsvinner noe av dette. Statiske medlemmer er derimot veldig nyttig om man vil gruppere felles oppførsel for en klasse når det gjelder ting som ikke nødvendigvis har noe med et enkelt objekt å gjøre.

En vanlig ting å gjøre med statiske metoder og medlemsvariabler er å implementere et såkalt Singleton-pattern. Det blir benyttet til å sørge for at det alltid kun finnes en instans av klassen. Her er en implementasjon;


<?php
class Singleton
{
    private static 
$singletonObject;
    
    public static function 
GetInstance()
    {
        if (!isset(
self::$singletonObject))
        {
            
self::$singletonObject = new self();
        }
        return 
self::$singletonObject;
    }
    
    public 
$variabel;
}

$singleton Singleton::GetInstance();
$singleton->variabel 3;
print_r($singleton);

$nr2 Singleton::GetInstance();
print_r($nr2);
?>

Statiske medlemsvariabler og metoder deklareres med nøkkelordet static, og det må skje etter du har angitt synligheten. Du trenger ikke å angi synlighet for statiske funksjoner og variabler, standard synlighet blir da satt til public.

Den vante variabelen $this er naturlig nok ikke tilgjengelig i statiske metoder, siden vi ikke har et objekt å jobbe på. I stedet benytter vi self (merk at det ikke er noe dollar-tegn foran navnet, siden det ikke er en variabel). self peker på klassen den kjører i, og vi kunne i dette tilfellet like gjerne ha byttet ut self med Singleton alle plasser vi har brukt det.

For å få tilgang til statiske metoder og variable, bruker vi en annen syntaks enn vi gjør på objekter. Klassenavnet, umiddelbart etterfulgt av to kolon, og deretter variabelnavnet (med dollartegnet foran) eller funksjonsnavnet gjør trikset.

Det kan være verdt å bite merke i at man fra en statisk metode har tilgang til både private, beskyttede og offentlige variabler og metoder i objekter laget av den samme klassen. Eksempelet under skriver ut tallene 1, 2 og 3;


<?php
class TilgangsTest
{
    public static function 
BrukObject($objekt)
    {
        echo 
$objekt->var1,"\n";
        echo 
$objekt->var2,"\n";
        echo 
$objekt->var3,"\n";
    }
    private 
$var1 1;
    protected 
$var2 2;
    public 
$var3 3;
}
$a = new TilgangsTest();
TilgangsTest::BrukObject($a);
?>

Det finnes selvsagt mange andre bruksområder for statiske metoder og variable, men dette gir deg en grunnleggende innføring i prinsippet bak dem. Singleton-klassen vår over er ikke komplett, men det finns to gode implementasjoner i PHP-manualen (den beste finnes i kommentarene nederst på siden).

Side 6: Objekt-operasjoner

Objekt-operasjoner

Den oppmerksomme leser har gjerne lagt merke til at objekter på en måte er en utvidelse av array-konseptet, og det er derfor en rekke operasjoner man kan utføre på objekter som man også kan gjøre på arrayer.

Ved å bruke en standard foreach-løkke kan man gå gjennom alle de synlige medlemsvariablene i et objekt. Det gjør det mulig å skrive ut innholdet i et objekt på en enkel måte. Legg merke til at det stod synlige medlemsvariabler over; om du forsøker dette i en metode som ikke er en del av klassen objektet tilhører vil du kun se medlemsvariabler merket public.

Et eksempel på denne typen objekt-iterasjon finner du i PHP-manualen.

Serialisering

serialize() er en funksjon som kan ta en hvilken som helst variabel i PHP og gjøre den om til en verdi som du kan lagre. Funksjonen blir som oftest brukt sammen med arrayer, men vi gjennomgikk den ikke da vi så på disse. Den brukes sammen med unserialize(), som tar den lagrede verdien og gjør den om til en PHP-variabel igjen.

serialize() går litt utenfor det vi har lært til nå, og lagrer både private, beskyttede og offentlige verdier i et objekt. Dette er nyttig om vi har et stort objekt som vi vil sende over nettet, eller lagre til senere.

En viktig ting å bite merke i, er at for å rekonstruere et objekt fra en slik verdi trenger man også klassedeklarasjonen. Det er kun objekt-verdiene som blir lagret i den serialiserte strengen, og metoder og klassestruktur ellers blir ikke lagret. Det må man gjerne tenke over når man overfører objekter på denne måten mellom ulike systemer; klassen må finnes i begge ender.

Her er et raskt eksempel som viser serialisering og rekonstruering av et objekt;



<?php
class Test
{
    private 
$private "a";
    protected 
$protected "b";
    public 
$public "c";
}
$a = new Test();
$a->public "ny verdi";
$serialisert serialize($a);
$objekt unserialize($serialisert);
echo 
$objekt->public;
?>

Husk at serialize() og unserialize() også fungerer på arrayer, og at det er her de er mest brukt.

Side 7: Oppsummering

Oppsummering

I dag har vi introdusert helt nye konsepter; klasser og objekter. Disse gjør det mulig å gruppere data og funksjonalitet i en og samme innkapsling, og er svært mye brukt i større systemer. Du vil også komme over dem i PHP-manualen med jevne mellomrom.

Om du ønsker å se mer på hvordan objekt-systemet i PHP fungerer, er denne siden i PHP-manualen å anbefale på det sterkeste.

Neste gang begynner vi å se på lagring i databaser, ved å bruke databasesystemet MySQL. Det er svært mye vi skal se på før vi kommer til hvordan vi integrerer MySQL med PHP, siden databaser er et relativt avansert og annerledes emne enn programmering.