PHP - Objektum orientált megvalósítás



Már több éve programozok PHP implementálási nyelven, és azon belül is alkalmaztam már különböző módszereket és technikákat. A PHP 5-ös verziója óta teljesen támogatottá vállt az objektum orientált megvalósítás. Azóta már használtam is főként adatbáziskezelés, képfeltöltés vagy éppen munkamenet kezeléshez, de eddig soha nem tisztáztam le magamba minden egyes lehetőségét, amit rejt magába ez a lehetőség. Ezen az oldalon összeírom mindazt, amit tudni érdemes erről a technikáról PHP-ben.

Osztály

Egy osztály tulajdonságokból és metódusokból áll. Egy osztály létrehozásánál érdemes névkonvenciót használni mi szerint az osztály neve kezdődjön betűvel vagy aláhúzással, valamint több szó esetén írjuk egybe és minden egyes szó kezdődjön nagybetűvel. Példaként implementáljunk egy "Ember" osztályt:

class Ember {
  /* 
  az osztály definíciója
  ide írhatjuk a tulajdonságokat és metódusokat 
  */
}

Az osztály létrehozása után példányosítható egy Ember osztályhoz tartozó új objektum:

$egy_uj_ember = new Ember();


Tulajdonság vagy más néven attribútum  


A tulajdonságok lényegében osztályokon belüli változók, melyeknek három korlátozási szintje van:

  • Public (bárhonnan elérhető, elhagyható, helyette lett var)
  • Private (osztályon belül érhető el)
  • Protected (osztályon és azok leszármazottain belül használható)
Tulajdonság létrehozása például:

class Ember {
  public $nev;
}

Megjegyzés: ha tulajdonságot nem látjuk el láthatósági előtaggal automatikusan public típusú lesz.
Egyszerű módon a tulajdonság értéke beállítható az objektum segítségével, amennyiben az publikusra van állítva az alábbi módon:

$egy_uj_ember->nev = "Dávid"

Vagy lekérdezhető és beletölthető egy változóba:

$ember_neve = $egy_uj_ember->nev;

Ezt így ilyen formában nem szokás, hanem a tulajdonságokat private vagy protect szintűre kell állítani és metódusokkal lekérdezni vagy beállítani azokat:

class Ember {

  private $nev;

  public function setNev($neve_legyen) {
    $this->nev = $neve_legyen;
  }
  public function getNev() {
    return $this->nev;
  }
}
$egy_uj_ember->setNev("Dávid");
$ember_neve = $egy_uj_ember->getNev(); // = Dávid

A metódusokon kívüli tulajdonságokra a $this kulcsszóval tudunk hivatkozni.

Konstruktor metódus

Ennek a metódusnak a lényege, hogy példányosítás során automatikusan be tudjuk állítani az osztály tulajdonságait. Magyarul, ha létrehozunk egy objektumot az osztályból, akkor a konstruktor metódus mindig lefut és segítségével beállíthatunk például kezdeti értékeket. A metódus neve kötött: __construct()

A példánál maradva:

class Ember {
  private $nev;
  private $kor;
  function __construct($par_nev, $par_kor) {
    $this->nev = $par_nev;
    $this->kor = $par_kor;
  }
}

Látható, hogy a metódus paramétereiben átadott értékekkel teszi egyenlővé a tulajdonságok értékeit.


Metódusok

Osztályokon belüli "tevékenységek" leírására metódusokat kell készíteni, melyeknek a tulajdonsághoz hasonlóan beállíthatjuk a láthatósági szintjét:
  • Public (bárhonnan elérhető, elhagyható)
  • Private (osztályon belül érhető el)
  • Protected (osztályon és azok leszármazottain belül használható)
Például:

class Ember {
  private $nev;
  function __construct($par_nev) {
    $this->setNev($par_nev);
  }
  private function setNev($par_nev) {
    $this->nev = $par_nev;
  }
}

A setNev privát metódus az Ember osztály $nev tulajdonságát állítja be a konstruktorral. A láthatósági szint miatt a setNev metódus az osztályon kívül nem használható.

Öröklődés

Osztályokat szükség szerint ki lehet bővíteni más osztályokkal öröklődéssel az extends kulcsszóval. Szülőosztálynak hívják azt az osztályt, ahonnan öröklődik egy újabb és gyerek osztályoknak azokat, amiket származtatunk a szülő osztályból. A gyerek így a szülőtől örököl bizonyos tulajdonságokat és metódusokat, amiket már nem kell újra létrehozni.

class Ember {
  private $nev;
  function __construct($par_nev) {
    $this->setNev($par_nev);
  }
  protected function setNev($par_nev) {
    $this->nev = $par_nev;
  }
}

class Ugyfel extends Ember {
  public $id;

  function __construct($par_nev, $par_azon) {
   $this->setNev($par_nev);
   $this->id = $par_azon;
  }
}
A példán látható, hogy a szülő osztály az Ember, a gyerek osztály pedig az Ugyfel. A setNev metódus láthatóságát private-ról át kell állítani protected-re, hogy a leszármazottakban is használhassuk. Fontos még azt is megjegyezni, hogy az a konstruktor fog végrehajtódni, amelyik osztályból példányosítunk. Az Ugyfel osztály $id tulajdonságát publikusra állítottam, így könnyedén lekérdezhető:

$uj_ugyfel = new Ugyfel("Dávid", 12);
$nev = $uj_ugyfel->get_nev(); //Dávid
$id = $ujember->id;   //12


Szülő osztály metódus felülírása

Előfordulhat, hogy egy származtatott gyerekosztály bizonyos metódusát szeretnénk használni, de egy kicsit módosítva azt. Ilyenkor egész egyszerű dolgunk van: csupán le kell másolni a szülő osztály metódusát és beletenni a gyerek osztályba. Amennyiben a származtatott osztályból példányosítunk, akkor annak metódusa fog lefutni, melyet bármire módosíthatunk. Példa:


class Ember {
protected $nev;
protected $kor;
function __construct($par_nev) {
$this->setNev($par_nev);
}
protected function setNev($legyen) {
$this->nev = $legyen;
}
function get_nev() {
return $this->nev;
}
function get_kor() {
return $this->kor;
}
}

class Ugyfel extends Ember{
public $id;
function __construct($par_nev, $par_id) {
$this->setNev($par_nev);
$this->id = $par_id;
}
protected function setNev($legyen) {
$this->nev = strtoupper($legyen);
}
}

A példában látható, hogy a setNev metódus a származtatott Ugyfel osztályba is megtalálható és működése eltér a szülő osztályétól, hogy a nev tulajdonság értékét csupa nagybetűsre álltja.
Na de mi van akkor, ha mégis ugyanazt a metódust szeretnék használni a gyerek osztályban is? Ilyenkor egy egyszerű szintaktikai elemmel tudatni kell a PHP-vel, hogy mi a szülő osztály metódusát szeretnénk használni:

Ember::setNev($par_nev);

Ezzel a technikával más osztályokból is lehet metódust meghívni. Itt fontos megjegyezni, hogyha biztosak vagyunk benne, hogy származtatott osztály hív szülő osztálybeli metódust, akkor használhatjuk így is a meghívást:

parent::setNev($par_nev);

Statikus változók

Statikus változónak vagy metódusnak akkor van értelme, ha konkrétan azt szeretnénk, hogy az adott változó vagy metódus az osztályhoz tartozzon és nem példányosított objektumhoz.
Deklarálása a static kulcsszóval történik osztályon belül, leggyakrabban ott, ahol a tulajdonságokat vesszük fel, például:

class Ember {
protected $nev;
protected $kor;
        public static $szamlalo;
function __construct($par_nev) {
$this->setNev($par_nev);
}
}
Statikus változó értékét osztályon belül a self kulcsszóval tudjuk megváltoztatni például így:

self::$szamlalo += 1;

Ha például ezt a konstruktorba tesszük, akkor minden egyes objektum létrehozásánál a $szamlalo értéke egyel nő. Érték kiolvasása: 

Ember::$szamlalo;

Osztályon belül a változóra a self::$valtozo -val hivatkozhatunk.

Statikus metódusok

A statikus metódusokat is a static szócskával kell ellátni és meghívásuk hasonlóképpen néz ki, mint a statikus változóké:

Osztaly::statikusMetodus();

Figyelem!! Nem szabad nem statikus metódust ilyen módszerrel meghívni!

Osztályon belül egy publikus statikus metódus példa:

public static function szamlaloKiir() {
  print "Szamlalo: ". self::$szamlalo;
}


Konstansok

A konstansok olyan változók osztályon belül, melyeknek értékei sosem változnak. A const szócskával kell implementálni. Az ilyen változók nevét nagybetűvel szokták írni. Használatakor nem fog kelleni a $ jel;

Például:

class Osztaly {
  const ORAKSZAMA = 24;
  
  function getMetodus() {
   print self::ORAKSZAMA;
  }
}

Osztályon belül a self::ORAKSZAMA szintaktikával hivatkozhatunk. Öröklődésnél ha szülő osztályban implementáljuk a konstans változót, akkor az ugyanúgy öröklődik a gyerek osztályban, mint bármi más. Ha felül akarjuk írni a gyerek osztályban, akkor létre kell hozni ugyanolyan névvel és akkor már azzal az értékkel fog számolni. A változó hivatkozása működik így is: Osztaly::ORAKSZAMA. Ez még akkor is működik, ha a konstansot a szülő osztályban hoztam létre és gyerek osztállyal hivatkozok rá.

Absztrakt osztályok és metódusok

Absztrakt osztályból objektum nem példányosítható. Az ilyen osztály csupán egy szerkezetet ad, hogy milyen tulajdonságok és metódusok vannak.

Létrehozhatunk absztrakt metódusokat is az osztályon belül, melyeket nem az absztrakt osztályon belül kell megírni, hanem a származtatottban. Mindenképpen meg kell írni, ha már létrehozzuk, különben hibára fut a PHP. Példa:

abstract class SzuloOsztaly {

  abstract function valamiMetodus();

}
class GyerekOsztaly extends SzuloOsztaly {

  function valamiMetodus() {
    echo "Kötelező metódus";
  }

}

Interfészek

Interfészek nem tartalmazhatnak tulajdonságokat csak konstansokat, metódusokat és az interface kulcsszóval hozhatjuk létre, származtatását pedig az implements szóval tehetjük meg. Több interfészt is "kaphat" egy osztály. Ilyenkor ezeket vesszővel kell elválasztani egymástól.

Automatikusan lefutó metódusok (Magic methods)

Mindegyik metódus szintaktikailag megegyező két alsó vonallal kezdődik majd utána pedig jön a neve. Korábban már volt is ilyen metódus, a __construct, de most nézzük a többit:

A __get() akkor fut le, ha egy olyan tulajdonságra akarok hivatkozni, mely nem létezik:
function __get($parameter) {
  echo "A ".$parameter." nevű tulajdonság bizony nem létezik :(";
}

A következő a __set(), amely akkor fut le, ha egy olyan tulajdonságnak akarunk értéket adni, amely nem létezik. Példa:
function __set($nev, $ertek) {
  echo "A ".$nev." nevű tulajdonság értéke nem állítható be ".$ertek."-re";
}

A metódusok vizsgálatára a __call() metódust használhatjuk, mely akkor hajtódik végre, amikor egy nem létezőre hivatkozunk:
function __call($nev, $parameterek) {
  echo "A ".$nev." nevű metódus nem létezik";
  print_r($parameterek);
}

További metódusok:

Ha egy objektumot szövegként ki szeretnénk íratni, akkor használhatjuk a __toString() metódust.

Ha egy objektumot le szeretnénk egy az egyben másolni akkor a __clone()-t kell használni:
$uj_masolt_objektum = clone $objektum;

Az __invoke() akkor fut le, amikor egy objektumot metódusként akarnánk futtatni.

A __destruct() pedig akkor hajtódik végre, amikor egy objektum "elpusztul". Ez két esetben lehetséges, hogy véget ér az oldal betöltése, lefut az algoritmus és véget ér az objektum élete vagy pedig kiadjuk az unset() parancsot, mely megsemmisíti az példányt.




Megjegyzések