Kihagyás

11. fejezet

A fejezet anyaga

Objektumorientált programozás

A PHP nyelvi szinten támogatja az objektumorientált paradigma lehetőségeit. Ennek köszönhetően a Programozás-I kurzuson megismert objektumorientált koncepciók PHP-ban is megvalósíthatók. Ebben az anyagrészben megnézzük, hogy hogyan.

Osztályok, objektumok

Osztályok létrehozása

Az osztály absztrakt fogalom, konkrét életbeli objektumok, objektumcsoportok formai leírása. Az osztályok segítségével olyan objektumokat formalizálhatunk, amelyek azonos tulajdonságokkal és viselkedéssel rendelkeznek.

PHP-ban egy osztályt a következő szintaxissal hozhatunk létre:

1
2
3
4
5
<?php
  class OsztalyNeve {
    // az osztály törzse...
  }
?>
Adattagok, metódusok

Az osztályaink adattagokat (tagváltozókat) és metódusokat (tagfüggvényeket) tartalmazhatnak. Ezeknek az elérhetőségét különböző láthatósági módosítószavakkal (access modifiers) szabályozhatjuk:

  • private: az adattag vagy a metódus csak az adott osztályból elérhető
  • protected: az adattag vagy a metódus csak az adott osztályból és annak gyermekosztályaiból (lásd: öröklődés) elérhető
  • public: az adattag vagy a metódus mindenhonnan elérhető.

PHP-ban alapból minden adattag és metódus public láthatóságú. Ez azt jelenti, hogy ha nem adjuk meg expliciten egy adattag vagy egy metódus láthatóságát, akkor az mindenhonnan elérhető lesz.

Mivel az adattagok lényegében az osztályhoz tartozó változók, ezért a nevük elé dollárjelet ($) írunk. A metódusok az osztály viselkedését leíró függvények, ezért őket a függvényeknél tanult function kulcsszóval hozzuk létre.

PHP-ban az osztályok adatagjait és metódusait a -> (nyíl) operátorral érhetjük el.

Példa: Az Ember osztály elkezdése

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
  class Ember {
    // adattagok
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    // egy metódus az osztályon belül
    public function koszon() {
      echo "Szia!";
    }
  }
?>
Konstruktor

Az osztályunk egyik speciális metódusa a konstruktor. Ez akkor fut le, amikor az osztályból egy konkrét objektumpéldányt készítünk, vagy másképp mondva az osztályt példányosítjuk.

PHP-ban az osztály konstruktora a __construct() nevet viseli. Jellemzően a konstruktorban szoktuk elvégezni az osztály adattagjainak inicializálását (tehát az adattagok kezdőértékkel való ellátását).

A $this beépített kulcsszó segítségével hivatkozhatunk magára az aktuális objektumra (ez nagyjából a Javából ismerős this-nek felel meg). Fontos, hogy (Javával ellentétben) PHP-ban minden esetben ki kell írnunk a $this kulcsszót, amikor az aktuális objektum egy adattagjára vagy metódusára szeretnénk hivatkozni!

Példa: Az Ember osztály konstruktorának megírása és a koszon() metódus kiegészítése az adattagok értékének kiíratásával

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {     // konstruktor
      $this->nev = $nev;      // adattagok inicializálása a konstruktor paraméterei alapján
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }

    public function koszon() {
      echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>";
    }
  }
?>

Figyelem

Továbbra se felejtsük el, hogy PHP-ban nem definiálhatunk a kódban több azonos nevű függvényt (még eltérő paraméterezés esetén sem)! Ha szeretnénk, hogy a konstruktort (vagy egy egyéb metódust) eltérő paraméterszámmal is meg lehessen hívni, akkor használjuk a default függvényparamétereket!

Például az alábbi konstruktort 1, 2 és 3 darab paraméterrel is meghívhatjuk.

1
2
3
4
5
6
<?php
  class Ember {
    // ...
    public function __construct($nev, $eletkor=30, $foglalkozas="munkanélküli") { /* ... */ }
  }
?>
Destruktor, getter, setter

A konstruktor mellett egy másik speciális metódus a destruktor, ami az objektumpéldány megszűnésekor fog lefutni. Desktruktort a __destruct() segítségével írhatunk PHP-ban.

Természetesen PHP-ban is van lehetőségünk getter és setter metódusokat készíteni. Ezeket jellemzően az osztály nem publikus adattagjaihoz írjuk. Emlékeztetőül: a getter az adattagok értékének lekérdezésére, míg a setter az adattagok értékének beállítására szolgál.

Példa: Létrehozunk egy destruktort az Ember osztályon belül, valamint az $eletkor adattaghoz írunk egy gettert és egy settert

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {     // konstruktor
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
      echo "--- " . $this->nev . " létrehozva. --- <br/>";
    }

    public function __destruct() {         // destruktor
      echo "--- Viszlát, " . $this->nev . "! --- <br/>";
    }

    public function getEletkor() {         // getter
      return $this->eletkor;
    }

    public function setEletkor($ertek) {  // setter
      if ($ertek > 0)                     // opcionális hibakezelés
        $this->eletkor = $ertek;
      else
          $this-> eletkor = 30;
    }

    public function koszon() {
      echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>";
    }
  }
?>
Objektumok létrehozása

A példányosítás során egy meglévő osztályból objektumpéldányt készítünk. Tehát az általános formai leírásunknak (ami az osztályunk) egy konkrét példányát készítjük el.

PHP-ban a példányosítás a new kulcsszóval történik. Ekkor meghívjuk a példányosítani kívánt osztály konstruktorát, amelynek rendre átadjuk a szükséges paramétereket.

1
2
3
4
<?php
  // a példányosítás (objektum létrehozás) szintaxisa PHP-ban
  $objektumNeve = new OsztalyNeve(/* konstruktor paraméterek */);
?>

Példa: Az Ember osztály példányosítása

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {     // konstruktor
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
      echo "--- " . $this->nev . " létrehozva. --- <br/>";
    }

    public function __destruct() {                        // destruktor
      echo "--- Viszlát, " . $this->nev . "! --- <br/>";
    }

    public function getEletkor() {                        // getter
      return $this->eletkor;
    }

    public function setEletkor($ertek) {                  // setter
      if ($ertek > 0)
        $this->eletkor = $ertek;
      else
          $this-> eletkor = 30;
    }

    public function koszon() {
      echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>";
    }
  }

  // példányosítás (objektumok létrehozása)

  $ember1 = new Ember("Jónás", 21, "kőtolvaj");     // konstruktor hívás
  $ember2 = new Ember("Noémi néni", 40, "virágboltos");

  echo $ember1->getEletkor() . "<br/>";             // getter teszt
  $ember2->setEletkor(60);                          // setter teszt
  $ember1->koszon();                                // koszon() metódus tesztelése
  $ember2->koszon();

  // a kód végén lefut a destruktor...
?>

A kód kimenete

--- Jónás létrehozva. --- --- Noémi néni létrehozva. --- 21 Szia! Jónás vagyok, egy 21 éves kőtolvaj. Szia! Noémi néni vagyok, egy 60 éves virágboltos. --- Viszlát, Noémi néni! --- --- Viszlát, Jónás! ---

Objektumok összehasonlítása

Objektumok összehasonlítására használhatjuk a korábban tanult == (dupla egyenlő) és === (tripla egyenlő) operátorokat.

  • A == operátor pontosan akkor ad vissza igazat, ha a két objektum adattagjainak a neve és értéke rendre megegyezik.
  • A === operátor pontosan akkor ad vissza igazat, ha a két objektum ugyanaz az objektum.

Példa: Objektumok összehasonlítása

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }
  }

  $ember1 = new Ember("Balázs", 20, "asztronauta");
  $ember2 = new Ember("Balázs", 20, "asztronauta");
  $ember3 = $ember1;

  var_dump($ember1 == $ember2); echo "<br/>";         // bool(true)
  var_dump($ember1 === $ember2); echo "<br/>";        // bool(false)
  var_dump($ember1 === $ember3); echo "<br/>";        // bool(true)
?>
Objektumok másolása

Tekintsük az előző kódpéldában az $ember3 = $ember1 értékadást! PHP-ban az objektumok alapértelmezetten referencia szerint működnek, ezért itt az $ember3 egy referencia lesz, ami ugyanarra az objektumra mutat, mint az $ember1. Ekkor tehát nem jön létre egy új objektum.

A referencia szerinti működésnek az egyik következménye, hogy ha a példában az $ember1 vagy $ember3 objektumok közül az egyiket valahogyan módosítjuk (pl. megváltoztatjuk az ember életkorát), akkor a másik objektum is ugyanígy fog változni.

Példa: Az objektumok referencia szerint működnek

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }

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

    public function setEletkor($ertek) { $this->eletkor = $ertek; }

    public function koszon() { echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>"; }
  }

  $ember1 = new Ember("Károly", 20, "gyártulajdonos");
  $ember2 = $ember1;
  $ember2->setEletkor(40);          // az $ember2 életkorát módosítjuk...
  $ember1->koszon();                // ...viszont az $ember1 életkora is módosul!
  $ember2->koszon();
?>

A kód kimenete

Szia! Károly vagyok, egy 40 éves gyártulajdonos. Szia! Károly vagyok, egy 40 éves gyártulajdonos.

Ha azt szeretnénk, hogy egy teljesen új objektumot hozzunk létre, amit az eredeti objektumtól függetlenül módosíthatunk, akkor használjuk a clone kulcsszót!

Példa: Objektum klónozása

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }

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

    public function setEletkor($ertek) { $this->eletkor = $ertek; }

    public function koszon() { echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>"; }
  }

  $ember1 = new Ember("Károly", 20, "gyártulajdonos");
  $ember2 = clone $ember1;  // most már $ember1 és $ember2 egymástól függetlenül módosítható
  $ember2->setEletkor(40);
  $ember1->koszon();
  $ember2->koszon();
?>

Kimenet

Szia! Károly vagyok, egy 20 éves gyártulajdonos. Szia! Károly vagyok, egy 40 éves gyártulajdonos.

Az osztályunkban definiálhatunk egy __clone() nevű metódust is, amely a clone kulcsszó hatására automatikusan meghívásra kerül. Ebben lehetőségünk van a létrejövő másolat objektum adattagjainak módosítására is.

Példa: A klónozás során 10 évvel növeljük az életkort

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }

    public function __clone() {       // a clone utasítás hatására fog meghívódni
      $this->eletkor += 10;
    }

    public function koszon() { echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>"; }
  }

  $karoly = new Ember("Károly", 20, "gyártulajdonos");
  $azIdosebbKaroly = clone $karoly;   // klónozás (__clone meghívása)
  $karoly->koszon();
  $azIdosebbKaroly->koszon();
?>

A kód kimenete

Szia! Károly vagyok, egy 20 éves gyártulajdonos. Szia! Károly vagyok, egy 30 éves gyártulajdonos.

A const és static kulcsszavak

A const kulcsszóval konstans adattagokat hozhatunk létre egy osztályon belül. A konstans adattagok neve elé nem írunk dollárjelet, és természetesen a kezdőértékük sem változhat meg (megjegyzendő viszont, hogy öröklődéssel a konstansok felüldefiniálhatók).

A konstans adattagok elérése az osztályon belül a self::ADATTAG_NEVE szintaxissal, az osztályon kívül pedig az OsztalyNeve::ADATTAG_NEVE segítségével lehetséges.

Példa: Konstans adattag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
  class Kor {
    private $r;             // a kör sugara
    const PI = 3.14;        // a pí értékét konstansként adjuk meg

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

    public function kerulet() { 
      return 2 * $this->r * self::PI; // konstans adattag elérése az osztályon belül
    }
  }

  $kor1 = new Kor(10.0);
  echo Kor::PI . "<br/>";             // konstans adattag elérése az osztályon kívül
  echo $kor1->kerulet() . "<br/>";
?>

A kód kimenete

3.14 62.8

A static kulcsszóval statikus adattagokat is készíthetünk egy osztályban. Az ilyen adattag egyetlen példányban léteznek a memóriában, és az osztály minden példánya osztozik rajta. Tehát tulajdonképpen a statikus adattagok valójában nem is az objektumhoz, hanem magához az osztályhoz tartoznak.

A statikus adattagokra az osztályon belül a self::$adattagNeve szintaxissal, az osztályon kívül pedig az OsztalyNeve::$adattagNeve (vagy $objektum::$adattagNeve) segítségével hivatkozhatunk szabályosan.

Példa: Statikus adattag, amivel számon tartjuk, hogy hány Ember objektumot hoztunk létre

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
  class Ember {
    private $nev;
    private $eletkor;
    protected $foglalkozas;
    public static $emberekSzama = 0;      // statikus adattag

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
      self::$emberekSzama++;    // statikus adattag elérése az osztályon belül
    }

    public function __destruct() {
      self::$emberekSzama--;    // statikus adattag elérése az osztályon belül
    }
  }

  $ember1 = new Ember("Dávid", 22, "kereskedő");
  $ember2 = new Ember("János", 42, "hegymászó");
  $ember3 = new Ember("Attila", 50, "metálénekes");

  echo Ember::$emberekSzama . " emberünk van. <br/>";   // statikus adattag elérése az osztályon kívül (1.)
  echo $ember2::$emberekSzama . " emberünk van. <br/>"; // statikus adattag elérése az osztályon kívül (2.)
?>

Kimenet

3 emberünk van. 3 emberünk van.

Megjegyzés

Természetesen nem csak az adattagok, hanem egy osztály metódusai is lehetnek statikusak. A statikus metódusokon belül a $this objektum nem érhető el!

Öröklődés

A Programozás-I kurzusról ismerős lehet számunkra az öröklődés fogalma. Ez egy osztályok közötti kapcsolat, amely egy úgynevezett ősosztály (szülőosztály) és egy gyermekosztály (leszármazott osztály) között valósul meg. A gyermekosztály tulajdonképpen az ősosztályának egy speciális változata lesz (pl. a Kutya osztály az Allat osztály gyermeke, hiszen minden kutya egyben állat is).

Az öröklődés során a gyermekosztály megörökli az ősosztályának az összes adattagját és metódusát. A gyermekosztályban létrehozhatunk az örökölteken kívül további adattagokat és metódusokat is, valamint lehetőségünk van az örökölt metódusok működésének felüldefiniálására is (overriding).

PHP-ban az öröklődést a következő szintaxissal valósíthatjuk meg:

1
2
3
4
5
<?php
  class GyermekOsztaly extends OsOsztaly {
    // gyermekosztály törzse...
  }
?>

Fontos megjegyezni, hogy PHP-ban csak egyszeres öröklődés van, azaz egy osztálynak nem lehet kettő vagy több ősosztálya. Ez természetesen nem zárja ki azt, hogy egy osztálynak több gyermekosztálya legyen, vagy hogy a gyermekosztálynak lehessenek saját gyermekosztályai.

A parent kulcsszóval hivatkozhatunk a gyermekosztályból az ősosztályra, annak adattagjaira és metódusaira: parent::$adattagNeve, parent::metodusNeve().

Példa Öröklődés - A Hallgato osztály az Ember osztály gyermeke, hiszen minden hallgató egyben ember is

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
  class Ember {                     // ősosztály
    private $nev;
    private $eletkor;
    protected $foglalkozas;

    public function __construct($nev, $eletkor, $foglalkozas) {
      $this->nev = $nev;
      $this->eletkor = $eletkor;
      $this->foglalkozas = $foglalkozas;
    }

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

    public function koszon() {
      echo "Szia! " . $this->nev . " vagyok, egy " . $this->eletkor . " éves " . $this->foglalkozas . ". <br/>";
    }
  }

  class Hallgato extends Ember {    // gyermekosztály (a hallgató egy speciális ember lesz)
    private $neptun;                // egy új adattag létrehozása

    public function __construct($nev, $eletkor, $neptun) {
      parent::__construct($nev, $eletkor, "hallgató");  // ősosztály konstruktorának meghívása
      $this->neptun = $neptun;
    }

    public function koszon() {    // overriding: az ősosztályból örökölt metódus felüldefiniálása
      echo parent::getNev() . " (" . $this->neptun . ") vagyok, egyetemi hallgató. <br/>";
    }

    public function osztondij($atlag) { // egy új metódus definiálása
      if ($atlag > 3.0)
        echo parent::getNev() . " ösztöndíjban részesül. Hurrá! <br/>";
      else
        echo parent::getNev() . " sajnos nem kap ösztöndíjat. <br/>";
    }
  }

  $ember1 = new Ember("Margit", 72, "nyugdíjas");
  $ember2 = new Hallgato("Ricsi", 21, "NEP4LF");
  $ember3 = new Hallgato("Tomi", 24, "ASD123");

  $ember1->koszon();
  $ember2->koszon();
  $ember3->koszon();
  $ember2->osztondij(2.6);
  $ember3->osztondij(4.8);
?>

A kód kimenete

Szia! Margit vagyok, egy 72 éves nyugdíjas. Ricsi (NEP4LF) vagyok, egyetemi hallgató. Tomi (ASD123) vagyok, egyetemi hallgató. Ricsi sajnos nem kap ösztöndíjat. Tomi ösztöndíjban részesül. Hurrá!

Típusellenőrzés

Ha PHP-ban egy $obj objektumról el szeretnénk dönteni, hogy az Oszt osztály példánya-e, akkor az $obj instanceof Oszt konstrukciót használjuk.

Példa: Típusellenőrzés az instanceof kulcsszóval

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
  class Ember { /* az osztály törzse... */ }
  class Hallgato extends Ember { /* az osztály törzse... */ }

  $ember1 = new Ember(/* ... */);
  $ember2 = new Hallgato(/* ... */);

  var_dump($ember1 instanceof Ember); echo "<br/>";  // bool(true)
  var_dump($ember2 instanceof Ember); echo "<br/>";  // bool(true)
?>

A final kulcsszó

A final kulcsszót két fontosabb célra használhatjuk:

  • Metódusok esetén megakadályozhatjuk a metódus felüldefiniálását a gyermekosztályokban a segítségével.
  • Osztályok esetén megakadályozhatjuk az osztályból való örököltetést a használatával.

Ha egy final kulcsszóval ellátott metódust felül akarunk definiálni, vagy egy final kulcsszóval ellátott osztályból örököltetünk, akkor végzetes hibát (Fatal Error) kapunk.

Példa: A final kulcsszó használata

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
  class PeldaOsztaly1 {
    // ha egy gyermekosztályban felül akarjuk írni ezt a metódust, akkor végzetes hibát kapunk
    final public function neIrjFelul() { /* ... */ }
  }

  // ha ebből az osztályból örököltetünk egy másik osztályt, akkor végzetes hibát kapunk
  final class PeldaOsztaly2 {
    // ...
  }
?>

Az abstract kulcsszó

Az abstract kulcsszó segítségével absztrakt metódusokat, illetve absztrakt osztályokat hozhatunk létre PHP-ban.

Absztrakt metódusok

Az absztrakt metódusok olyan metódusok, amelyek nem rendelkeznek funkcióval az osztályon belül. Ezeket az absztrakt metódusokat a gyermekosztályban kell majd funkcióval ellátni.

Például ha van egy Allat osztályunk, aminek van egy hangotAd() metódusa, akkor ezt a metódust absztrakttá tehetjük, hiszen egy állatról nem tudjuk általánosságban megmondani, hogy milyen hangot ad. Viszont az Allat osztályból örököltetett Macska osztályról már pontosan tudjuk, hogy milyen hangot ad ("Miaú!"), ezért ebben az osztályban már implementáljuk a hangotAd() metódust.

Absztrakt osztályok

Ha egy osztály tartalmaz legalább 1 absztrakt metódust, akkor az osztály is kötelezően absztrakt kell, hogy legyen! Az abstract kulcsszóval ellátott osztályok nem példányosíthatók.

Az absztrakt osztályból származó gyermekosztályban az összes absztrakt metódust felül kell definiálni (ellenkező esetben a gyermekosztálynak is absztraktnak kell lennie)!

Példa: Absztrakt metódus és absztrakt osztály

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
  abstract class Allat {                  // absztrakt osztály
    protected $nev;

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

    abstract public function hangotAd();  // absztrakt metódus (nem rendelkezik funkcióval)
  }

  class Macska extends Allat {
    private $fajta;

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

    public function hangotAd() {  // absztrakt metódus felüldefiniálása (funkcióval való ellátása)
      echo "Miaú! Miaú! <br/>";
    }
  }

  // $allat = new Allat("Csőrike");    // HIBA: az absztrakt osztály nem példányosítható!
  $macska = new Macska("Szilveszter", "tuxedo cat");
  $macska->hangotAd();
?>

A kód kimenete

Miaú! Miaú!

Interfészek

PHP-ban az interface kulcsszóval létrehozhatunk interfészeket is, amelyek csak publikus absztrakt metódusokat és osztálykonstansokat tartalmazhatnak. Az interfészekben definiált absztrakt metódusok esetén az abstract kulcsszót nem írjuk ki.

Ha azt szeretnénk, hogy egy osztály megvalósítson (más szóval implementáljon) egy adott interfészt, akkor ezt az implements kulcsszóval tehetjük meg. Ekkor az interfészt megvalósító osztályban az interfész összes absztrakt metódusát felül kell definiálnunk, különben az osztályt absztrakttá kell tennünk.

Egy osztály egyszerre akár több interfészt is megvalósíthat (ekkor a megvalósított interfészeket vesszővel elválasztva adjuk meg az implements kulcsszó után). Emiatt az interfészeket használhatjuk akár a többszörös öröklődés hiányának megoldására is.

Tipp

A többszörös öröklődés hiányára egy másik megoldást nyújtanak a trait-ek. Róluk ezen a linken, illetve az előadáson tanulhatunk.

Példa: Interfészek használata

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
  // két interfész létrehozása
  interface Negylabu {
    const LABAK_SZAMA = 4;
  }

  interface Ragadozo {
    public function vadaszik();
    public function eszik($mit);
  }

  // egy Macska osztály létrehozása, ami mindkét interfészt megvalósítja
  class Macska implements Negylabu, Ragadozo {
    private $nev;

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

    // felüldefiniáljuk a megvalósított interfészek absztrakt metódusait
    public function vadaszik() { echo $this->nev . " vadászik... <br/>"; }

    public function eszik($mit) { echo $this->nev . " megette: " . $mit . ". <br/>"; }
  }

  // a Macska osztály példányosítása
  $cica = new Macska("Garfield");
  $cica->vadaszik();
  $cica->eszik("lasagne");
?>

A kód kimenete

Garfield vadászik... Garfield megette: lasagne.

Gyakorló feladat

Feladat

Egészítsük ki az előző fejezetekben elkezdett projektünket objektumorientáltsággal! A feladat egy felhasználói hozzászólások írására szolgáló funkció elkészítése a projekt főoldalán.

  1. Hozzunk létre egy Komment osztályt, amely egy felhasználói hozzászólást fog reprezentálni! Egy kommentről tároljuk el a hozzászólást író felhasználó nevét (továbbiakban: szerző), a kommentelés időpontját és a komment szövegét! Ezek legyenek privát adattagok, amelyekhez készítsünk getter és setter metódusokat! Egy új komment létrehozásakor a konstruktor paraméterben kapja a szerzőt, az időpontot és a komment szövegét, és ezekkel inicializálja az adattagokat.

  2. Hozzunk létre egy kezdetben üres comments.txt fájlt! Ebben a fájlban fogjuk tárolni a kommentek adatait szerializált formában. Írjuk meg a kozos.php-ban a loadComments($path) és saveComments($path, $comments) függvényeket! Az előbbi függvény a kommentek fájlból való betöltéséért, utóbbi pedig a hozzászólások fájlba történő elmentéséért felel.

  3. A főoldalon (index.php) listázzuk ki a meglévő kommenteket egy-egy <div>-ben! Jelenítsük meg a kommentet író felhasználó nevét és profilképét, a komment dátumát és szövegét! Amennyiben még nem érkezett egyetlen komment sem, akkor írassuk ki a "Még nem érkezett hozzászólás." szöveget ehelyett!

  4. A bejelentkezett felhasználók számára jelenítsünk meg a kommentek kilistázása alatt egy új komment írására alkalmas űrlapot! Helyezzünk el ezen egy több soros beviteli mezőt, valamint egy elküldés gombot!

  5. Miután egy felhasználó új kommentet írt, akkor dolgozzuk fel azt a következő szempontok szerint:

    • Ha a beviteli mezőt nem töltötte ki a felhasználó, akkor írassunk ki hibaüzenetet!
  6. Egy komment legfeljebb 500 karakter hosszú lehessen, ha ettől több karaktert ad meg a felhasználó a beviteli mezőben, akkor írassunk ki hibaüzenetet!

  7. Ha nem találtunk semmilyen hibát az űrlapfeldolgozás során, akkor a bejelentkezett felhasználó nevének, a beírt komment szövegének és az aktuális időpontnak (lásd: lentebb) a felhasználásával példányosítsunk egy új Komment objektumot! Mentsük el az új kommentet a comments.txt-be (saveComments() függvény), majd ezután töltsük újra a weboldalt!

Segítség az aktuális dátum és időpont lekéréséhez:

1
2
3
4
5
  <?php
    $datum = new DateTime();
    $datum->setTimezone(new DateTimeZone("Europe/Budapest"));         // időzóna megadása
    echo "A mai dátum és időpont: " . $datum->format('Y-m-d H:i:s');  // kiíratás ÉÉÉÉ-HH-NN ÓÓ:PP:MM formátumban
  ?>

Megoldás

A feladat egy lehetséges megoldása letölthető ide kattintva.

Kivételkezelés

A kivétel fogalma

A programvégrehajtás során bekövetkezhet olyan nem várt esemény, amely meggátolja a program futását az adott blokkban. PHP-ban ebben az esetben úgynevezett kivétel dobódik.

Példa: A nullával való osztás egy DivisionByZeroError típusú kivételt eredményez

1
2
3
4
<?php
  $szentsegtores = 5 / 0;
  echo "Mérges matematikus hangok...";
?>

A kód kimenete

Fatal error: Uncaught DivisionByZeroError: Division by zero in C:\xampp\htdocs\test.php:2 Stack trace: #0 {main} thrown in C:\xampp\htdocs\test.php on line 2

PHP-ban kétféle kivételtípust különböztetünk meg:

  • az Error-ok belső PHP hibákat reprezentálnak
  • az Exception-ök programozói hibákat jelölnek.

Az Error és az Exception osztályok mindketten a Throwable interfészt valósítják meg, amely minden PHP-beli kivétel közös interfésze. A beépített kivételek az Error vagy az Exception osztályokból származnak. Ezt a hierarchiát szemlélteti az alábbi ábra.

PHP kivételek

Kivétel dobása

A throw utasítással mi magunk is dobhatunk manuálisan egy kivételt. A throw kulcsszó után meghívjuk annak a kivételosztálynak a konstruktorát, amilyen típusú kivételt el szeretnénk dobni. PHP-ban (Javával ellentétben) sehol sem kell jelezni a kivételdobást.

1
2
3
<?php
  throw new Exception();    // Exception típusú kivétel dobása
?>

A kivételosztály példányosításakor a konstruktornak átadhatunk egy szöveget is paraméterben, ekkor ez lesz a kivétel üzenete (hibaüzenet).

1
2
3
<?php
  throw new Exception("Valamit elszúrtunk...");
?>

Kivételek kezelése

Ha a programban valahol egy kivétel el lett dobva, akkor azt el is tudjuk kapni. PHP-ban a kivételkezelésre a try, catch és finally utasításokat használjuk, a Javához hasonló módon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
  try {
    // a kód azon része, ahol kivétel dobódhat
  } catch (DivisionByZeroError $exc) {
    // DivisionByZeroError típusú kivétel elkapása ($exc-ként hivatkozhatunk rá)
  } catch (Exception $exc) {
    // Exception típusú kivétel elkapása ($exc-ként hivatkozhatunk rá)
  } catch (Error $err) {
    // Error típusú kivétel elkapása ($err-ként hivatkozhatunk rá)
  } finally {
    // mindenképpen lefutó kódrész
  }
?>

Több catch ág esetén a legelső olyan fog lefutni, amely a dobott kivétel típusára illeszkedik.

Példa: Írjunk egy osztás függvényt, amely két egész paramétert vár ($a és $b), visszatérési értéke pedig az $a/$b hányados! Ha nem egész típusú paramétereket kapunk vagy ha nullával szeretnénk osztani, akkor dobjunk kivételt!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
  function osztas($a, $b) {
    if (is_int($a) && is_int($b)) {
      if ($b === 0)
        throw new DivisionByZeroError("Ejnye, nullával nem osztunk!");  // kivétel dobása
      return $a / $b;
    }

    throw new TypeError("Egész paramétereket adj meg!");                // kivétel dobása
  }

  // kivételkezelés

  try {
    echo osztas(5, 2) . "<br/>";
    echo osztas(5, 0) . "<br/>";
    echo osztas(3, 2) . "<br/>";
  } catch (DivisionByZeroError $dze) {
    echo $dze->getMessage() . "<br/>";      // kivétel szövegének kiíratása
  } catch (TypeError $te) {
    echo $te->getMessage() . "<br/>";
  } catch (Error $err) {
    echo "Nem várt hiba történt a program futása során! <br/>";
  } catch (Exception $exc) {
    echo "Nem várt hiba történt a program futása során! <br/>";
  } finally {
    echo "--- Kivételkezelés vége. --- <br/>";
  }
?>

A kód kimenete

2.5 Ejnye, nullával nem osztunk! --- Kivételkezelés vége. ---

Saját kivételosztályok

Ha szeretnénk, akkor PHP-ban akár saját kivételosztályt is írhatunk. A létrehozni kívánt kivételosztályt valamely létező kivételosztályból kell örököltetnünk (jellemzően az Exception osztályból vagy annak valamelyik gyermekosztályából szoktunk öröklődni).

Példa: Saját kivételosztály írása

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
  class NincsPisztaciaException extends Exception {   // egy Exception-ből származó kivételosztály
    public function __construct($uzenet) {
      parent::__construct($uzenet);                   // ősosztály konstruktorának meghívása
    }
  }

  try {
    $fagyi_izek = ["vanília", "szamóca", "málna", "karamell"];

    if (!in_array("pisztácia", $fagyi_izek))
      throw new NincsPisztaciaException("A pisztácia kifogyott!");
  } catch (NincsPisztaciaException $npe) {
    echo $npe->getMessage();
  }
?>

A kód kimenete

A pisztácia kifogyott!


Utolsó frissítés: 2021-03-24 13:14:20