Bevezetés az objektumorientált programozásba
Az “OOP” kifejezés az objektumorientált programozást jelenti, amely a kód szervezésének és strukturálásának egy módja. Az OOP lehetővé teszi számunkra, hogy a programot egymással kommunikáló objektumok halmazaként tekintsük, nem pedig utasítások és függvények sorozataként.
Az OOP-ban az “objektum” egy olyan egység, amely adatokat és azokkal az adatokkal dolgozó függvényeket tartalmaz. Az objektumok “osztályok” alapján jönnek létre, amelyeket az objektumok terveiként vagy sablonjaiként foghatunk fel. Ha van egy osztályunk, létrehozhatjuk annak “példányát”, ami egy konkrét, az osztály alapján létrehozott objektum.
Nézzük meg, hogyan hozhatunk létre egy egyszerű osztályt PHP-ban. Az osztály definiálásakor a “class” kulcsszót használjuk, amelyet az osztály neve követ, majd kapcsos zárójelek, amelyek az osztály függvényeit (ezeket “metódusoknak” nevezzük) és változóit (ezeket “property-knek” vagy “tulajdonságoknak” nevezzük) foglalják magukba:
class Auto
{
function dudal()
{
echo 'Bip bip!';
}
}
Ebben a példában létrehoztunk egy Auto
nevű osztályt egyetlen dudal
nevű függvénnyel (vagy
“metódussal”).
Minden osztálynak csak egy fő feladatot kellene megoldania. Ha egy osztály túl sok mindent csinál, érdemes lehet kisebb, specializált osztályokra bontani.
Az osztályokat általában külön fájlokba mentjük, hogy a kód rendezett és könnyen áttekinthető legyen. A fájl
nevének meg kell egyeznie az osztály nevével, tehát az Auto
osztály esetében a fájl neve
Auto.php
lenne.
Az osztályok elnevezésekor jó gyakorlat a “PascalCase” konvenció követése, ami azt jelenti, hogy a név minden szava nagybetűvel kezdődik, és nincsenek közöttük aláhúzások vagy más elválasztójelek. A metódusok és property-k a “camelCase” konvenció szerint kisbetűvel kezdődnek.
Néhány PHP metódusnak speciális feladata van, és __
(két aláhúzás) előtaggal vannak jelölve. Az egyik
legfontosabb speciális metódus a “konstruktor”, amelyet __construct
-ként jelölünk. A konstruktor egy olyan
metódus, amely automatikusan meghívódik, amikor létrehozunk egy új osztálypéldányt.
A konstruktort gyakran használjuk az objektum kezdeti állapotának beállítására. Például, amikor egy személyt reprezentáló objektumot hozunk létre, a konstruktort használhatjuk a korának, nevének vagy más tulajdonságainak beállítására.
Nézzük meg, hogyan használhatunk konstruktort PHP-ban:
class Szemely
{
private $kor;
function __construct($kor)
{
$this->kor = $kor;
}
function hanyEvesVagy()
{
return $this->kor;
}
}
$szemely = new Szemely(25);
echo $szemely->hanyEvesVagy(); // Kiírja: 25
Ebben a példában a Szemely
osztálynak van egy $kor
property-je (változója) és egy konstruktora,
amely ezt a property-t állítja be. A hanyEvesVagy()
metódus ezután lehetővé teszi a személy korához való
hozzáférést.
A $this
pszeudo-változót az osztályon belül használjuk az objektum property-jeihez és metódusaihoz való
hozzáféréshez.
A new
kulcsszót használjuk egy új osztálypéldány létrehozásához. A fenti példában egy új, 25 éves
személyt hoztunk létre.
Beállíthatunk alapértelmezett értékeket is a konstruktor paramétereinek, ha azokat nem adjuk meg az objektum létrehozásakor. Például:
class Szemely
{
private $kor;
function __construct($kor = 20)
{
$this->kor = $kor;
}
function hanyEvesVagy()
{
return $this->kor;
}
}
$szemely = new Szemely; // ha nem adunk át argumentumot, a zárójelek elhagyhatók
echo $szemely->hanyEvesVagy(); // Kiírja: 20
Ebben a példában, ha nem adja meg a kort a Szemely
objektum létrehozásakor, az alapértelmezett 20-as érték
lesz használva.
Kellemes, hogy a property definíciója és annak konstruktoron keresztüli inicializálása így lerövidíthető és egyszerűsíthető:
class Szemely
{
function __construct(
private $kor = 20,
) {
}
}
A teljesség kedvéért, a konstruktorok mellett az objektumoknak lehetnek destruktoraik is (a __destruct
metódus), amelyek azelőtt hívódnak meg, mielőtt az objektum felszabadulna a memóriából.
Névterek
A névterek (angolul “namespaces”) lehetővé teszik számunkra, hogy a kapcsolódó osztályokat, függvényeket és konstansokat szervezzük és csoportosítsuk, miközben elkerüljük a névütközéseket. Úgy képzelhetjük el őket, mint mappákat a számítógépen, ahol minden mappa egy adott projekthez vagy témához tartozó fájlokat tartalmaz.
A névterek különösen hasznosak nagyobb projektekben, vagy amikor harmadik féltől származó könyvtárakat használunk, ahol osztálynév-ütközések léphetnek fel.
Képzeljük el, hogy van egy Auto
nevű osztályunk a projektünkben, és szeretnénk azt egy Doprava
nevű névtérbe helyezni. Ezt így tehetjük meg:
namespace Doprava;
class Auto
{
function dudal()
{
echo 'Bip bip!';
}
}
Ha az Auto
osztályt egy másik fájlban szeretnénk használni, meg kell adnunk, hogy melyik névtérből
származik az osztály:
$auto = new Doprava\Auto;
Az egyszerűsítés érdekében a fájl elején megadhatjuk, hogy melyik osztályt szeretnénk használni az adott névtérből, ami lehetővé teszi a példányok létrehozását anélkül, hogy a teljes elérési utat meg kellene adni:
use Doprava\Auto;
$auto = new Auto;
Öröklődés
Az öröklődés az objektumorientált programozás egyik eszköze, amely lehetővé teszi új osztályok létrehozását már létező osztályok alapján, átvéve azok property-jeit és metódusait, és szükség szerint kiterjesztve vagy újradefiniálva azokat. Az öröklődés lehetővé teszi a kód újrafelhasználhatóságát és az osztályhierarchiát.
Egyszerűen fogalmazva, ha van egy osztályunk, és szeretnénk egy másikat létrehozni, amely abból származik, de néhány változtatással, akkor az új osztályt “örökölhetjük” az eredeti osztályból.
PHP-ban az öröklődést az extends
kulcsszóval valósítjuk meg.
A Szemely
osztályunk tárolja a kor információt. Lehet egy másik Diak
osztályunk, amely
kiterjeszti a Szemely
-t, és hozzáadja a tanulmányi szak információját.
Nézzünk egy példát:
class Szemely
{
private $kor;
function __construct($kor)
{
$this->kor = $kor;
}
function informacioKiirasa()
{
echo "Kor: {$this->kor} év\n";
}
}
class Diak extends Szemely
{
private $szak;
function __construct($kor, $szak)
{
parent::__construct($kor);
$this->szak = $szak;
}
function informacioKiirasa()
{
parent::informacioKiirasa();
echo "Tanulmányi szak: {$this->szak} \n";
}
}
$diak = new Diak(20, 'Informatika');
$diak->informacioKiirasa();
Hogyan működik ez a kód?
- Az
extends
kulcsszót használtuk aSzemely
osztály kiterjesztéséhez, ami azt jelenti, hogy aDiak
osztály örökli az összes metódust és property-t aSzemely
-től. - A
parent::
kulcsszó lehetővé teszi számunkra, hogy metódusokat hívjunk a szülő osztályból. Ebben az esetben aSzemely
osztály konstruktorát hívtuk meg, mielőtt saját funkcionalitást adtunk volna hozzá aDiak
osztályhoz. És hasonlóképpen az ősinformacioKiirasa()
metódusát is, mielőtt kiírtuk volna a diákra vonatkozó információkat.
Az öröklődés olyan helyzetekre való, amikor “egy” kapcsolat (is-a relationship) áll fenn az osztályok között.
Például a Diak
egy Szemely
. A macska egy állat. Lehetőséget ad nekünk arra, hogy olyan esetekben,
amikor a kódban egy objektumot (pl. “Szemely”) várunk, helyette egy öröklött objektumot (pl. “Diak”)
használjunk.
Fontos megjegyezni, hogy az öröklődés fő célja nem a kódduplikáció elkerülése. Éppen ellenkezőleg, az öröklődés helytelen használata bonyolult és nehezen karbantartható kódhoz vezethet. Ha az “egy” kapcsolat nem létezik az osztályok között, az öröklődés helyett a kompozíciót kellene fontolóra vennünk.
Vegyük észre, hogy a Szemely
és Diak
osztályok informacioKiirasa()
metódusai kissé
eltérő információkat írnak ki. És hozzáadhatunk további osztályokat (például Alkalmazott
), amelyek
további implementációkat biztosítanak ehhez a metódushoz. Azt a képességet, hogy különböző osztályok objektumai
ugyanarra a metódusra különböző módon reagáljanak, polimorfizmusnak nevezzük:
$szemelyek = [
new Szemely(30),
new Diak(20, 'Informatika'),
new Alkalmazott(45, 'Igazgató'),
];
foreach ($szemelyek as $szemely) {
$szemely->informacioKiirasa();
}
Kompozíció
A kompozíció egy olyan technika, amikor ahelyett, hogy egy másik osztály property-jeit és metódusait örökölnénk, egyszerűen felhasználjuk annak példányát a saját osztályunkban. Ez lehetővé teszi számunkra, hogy több osztály funkcionalitását és property-jeit kombináljuk anélkül, hogy bonyolult öröklődési struktúrákat kellene létrehoznunk.
Nézzünk egy példát. Van egy Motor
osztályunk és egy Auto
osztályunk. Ahelyett, hogy azt
mondanánk “Az Autó egy Motor”, azt mondjuk “Az Autónak van Motorja”, ami egy tipikus kompozíciós kapcsolat.
class Motor
{
function bekapcsol()
{
echo 'Motor fut.';
}
}
class Auto
{
private $motor;
function __construct()
{
$this->motor = new Motor;
}
function indit()
{
$this->motor->bekapcsol();
echo 'Az autó készen áll az indulásra!';
}
}
$auto = new Auto;
$auto->indit();
Itt az Auto
nem rendelkezik a Motor
összes property-jével és metódusával, de hozzáfér hozzá a
$motor
property-n keresztül.
A kompozíció előnye a nagyobb tervezési flexibilitás és a jövőbeli módosítások jobb lehetősége.
Láthatóság
PHP-ban definiálhatunk “láthatóságot” az osztály property-jeire, metódusaira és konstansaira. A láthatóság határozza meg, hogy honnan férhetünk hozzá ezekhez az elemekhez.
- Public: Ha egy elem
public
-ként van megjelölve, az azt jelenti, hogy bárhonnan hozzáférhetünk, akár az osztályon kívülről is. - Protected: A
protected
jelölésű elem csak az adott osztályon belül és annak minden leszármazottjában (azok az osztályok, amelyek ebből az osztályból örökölnek) érhető el. - Private: Ha egy elem
private
, akkor csak azon az osztályon belülről férhetünk hozzá, amelyben definiálva lett.
Ha nem adunk meg láthatóságot, a PHP automatikusan public
-ra állítja.
Nézzünk egy példakódot:
class LathatosagDemonstracio
{
public $nyilvanosTulajdonsag = 'Nyilvános';
protected $vedettTulajdonsag = 'Védett';
private $privatTulajdonsag = 'Privát';
public function tulajdonsagokKiirasa()
{
echo $this->nyilvanosTulajdonsag; // Működik
echo $this->vedettTulajdonsag; // Működik
echo $this->privatTulajdonsag; // Működik
}
}
$objektum = new LathatosagDemonstracio;
$objektum->tulajdonsagokKiirasa();
echo $objektum->nyilvanosTulajdonsag; // Működik
// echo $objektum->vedettTulajdonsag; // Hibát dob
// echo $objektum->privatTulajdonsag; // Hibát dob
Folytassuk az osztály öröklésével:
class OsztalyLeszarmazott extends LathatosagDemonstracio
{
public function tulajdonsagokKiirasa()
{
echo $this->nyilvanosTulajdonsag; // Működik
echo $this->vedettTulajdonsag; // Működik
// echo $this->privatTulajdonsag; // Hibát dob
}
}
Ebben az esetben az OsztalyLeszarmazott
osztály tulajdonsagokKiirasa()
metódusa hozzáférhet a
nyilvános és védett property-khez, de nem férhet hozzá a szülő osztály privát property-jeihez.
Az adatokat és metódusokat a lehető legjobban el kell rejteni, és csak egy definiált interfészen keresztül szabad hozzáférni hozzájuk. Ez lehetővé teszi az osztály belső implementációjának megváltoztatását anélkül, hogy a kód többi részét befolyásolná.
A final
kulcsszó
PHP-ban használhatjuk a final
kulcsszót, ha meg akarjuk akadályozni egy osztály, metódus vagy konstans
öröklését vagy felülírását. Ha egy osztályt final
-ként jelölünk meg, nem lehet kiterjeszteni. Ha egy
metódust final
-ként jelölünk meg, azt nem lehet felülírni egy leszármazott osztályban.
Annak tudata, hogy egy bizonyos osztály vagy metódus nem lesz tovább módosítva, lehetővé teszi számunkra, hogy könnyebben végezzünk módosításokat anélkül, hogy aggódnunk kellene a lehetséges konfliktusok miatt. Például hozzáadhatunk egy új metódust anélkül, hogy attól tartanánk, hogy valamelyik leszármazottjának már van egy azonos nevű metódusa, és ütközés történne. Vagy megváltoztathatjuk egy metódus paramétereit, mivel itt sem fenyeget az a veszély, hogy ellentmondást okozunk egy leszármazottban felülírt metódussal.
final class VeglegesOsztaly
{
}
// A következő kód hibát okoz, mert nem örökölhetünk a final osztályból.
class VeglegesOsztalyLeszarmazott extends VeglegesOsztaly
{
}
Ebben a példában a VeglegesOsztaly
final osztályból való öröklési kísérlet hibát okoz.
Statikus property-k és metódusok
Amikor PHP-ban “statikus” osztályelemekről beszélünk, olyan metódusokra és property-kre gondolunk, amelyek magához az osztályhoz tartoznak, nem pedig az osztály egy konkrét példányához. Ez azt jelenti, hogy nem kell létrehoznia az osztály egy példányát ahhoz, hogy hozzáférjen hozzájuk. Ehelyett közvetlenül az osztály nevén keresztül hívja meg vagy éri el őket.
Ne feledje, hogy mivel a statikus elemek az osztályhoz tartoznak, és nem annak példányaihoz, a statikus metódusokon belül
nem használhatja a $this
pszeudo-változót.
A statikus property-k használata átláthatatlan, buktatókkal teli kódhoz vezet, ezért soha ne használja őket, és itt nem is mutatunk példát a használatukra. Ezzel szemben a statikus metódusok hasznosak. Példa a használatra:
class Szamologep
{
public static function osszeadas($a, $b)
{
return $a + $b;
}
public static function kivonas($a, $b)
{
return $a - $b;
}
}
// Statikus metódus használata az osztály példányosítása nélkül
echo Szamologep::osszeadas(5, 3); // Eredmény: 8
echo Szamologep::kivonas(5, 3); // Eredmény: 2
Ebben a példában létrehoztunk egy Szamologep
osztályt két statikus metódussal. Ezeket a metódusokat
közvetlenül, az osztály példányának létrehozása nélkül hívhatjuk meg a ::
operátor segítségével.
A statikus metódusok különösen hasznosak olyan műveletekhez, amelyek nem függenek egy konkrét osztálypéldány
állapotától.
Osztálykonstansok
Az osztályokon belül lehetőségünk van konstansok definiálására. A konstansok olyan értékek, amelyek soha nem változnak a program futása során. A változókkal ellentétben a konstans értéke mindig ugyanaz marad.
class Auto
{
public const KerekekSzama = 4;
public function kerekekSzamaMegjelenitese(): int
{
echo self::KerekekSzama;
}
}
echo Auto::KerekekSzama; // Kimenet: 4
Ebben a példában van egy Auto
osztályunk a KerekekSzama
konstanssal. Ha az osztályon belül
szeretnénk hozzáférni a konstanshoz, az osztály neve helyett a self
kulcsszót használhatjuk.
Objektuminterfészek
Az objektuminterfészek “szerződésekként” működnek az osztályok számára. Ha egy osztálynak implementálnia kell egy objektuminterfészt, tartalmaznia kell az összes metódust, amelyet ez az interfész definiál. Ez egy nagyszerű módja annak biztosítására, hogy bizonyos osztályok ugyanazt a “szerződést” vagy struktúrát kövessék.
PHP-ban az interfészt az interface
kulcsszóval definiáljuk. Az interfészben definiált összes metódus public
(public
). Amikor egy osztály implementál egy interfészt, az implements
kulcsszót használja.
interface Allat
{
function hangotAd();
}
class Macska implements Allat
{
public function hangotAd()
{
echo 'Mňau';
}
}
$macska = new Macska;
$macska->hangotAd();
Ha egy osztály implementál egy interfészt, de nem definiálja az összes elvárt metódust, a PHP hibát dob.
Egy osztály egyszerre több interfészt is implementálhat, ami eltérés az öröklődéstől, ahol egy osztály csak egyetlen osztálytól örökölhet:
interface Orzo
{
function hazatOriz();
}
class Kutya implements Allat, Orzo
{
public function hangotAd()
{
echo 'Haf';
}
public function hazatOriz()
{
echo 'A kutya gondosan őrzi a házat';
}
}
Absztrakt osztályok
Az absztrakt osztályok alap sablonként szolgálnak más osztályok számára, de nem hozhat létre közvetlenül példányokat belőlük. Tartalmaznak teljes metódusok és absztrakt metódusok kombinációját, amelyeknek nincs definiált tartalmuk. Azok az osztályok, amelyek absztrakt osztályokból örökölnek, meg kell adniuk az összes absztrakt metódus definícióját az ősből.
Absztrakt osztály definiálásához az abstract
kulcsszót használjuk.
abstract class AbsztraktOsztaly
{
public function normalMetodus()
{
echo 'Ez egy normál metódus';
}
abstract public function absztraktMetodus();
}
class Leszarmazott extends AbsztraktOsztaly
{
public function absztraktMetodus()
{
echo 'Ez az absztrakt metódus implementációja';
}
}
$peldany = new Leszarmazott;
$peldany->normalMetodus();
$peldany->absztraktMetodus();
Ebben a példában van egy absztrakt osztályunk egy normál és egy absztrakt metódussal. Ezután van egy
Leszarmazott
osztályunk, amely az AbsztraktOsztaly
-ból örököl, és implementációt biztosít az
absztrakt metódushoz.
Miben különböznek valójában az interfészek és az absztrakt osztályok? Az absztrakt osztályok tartalmazhatnak absztrakt és konkrét metódusokat is, míg az interfészek csak azt definiálják, hogy egy osztálynak milyen metódusokat kell implementálnia, de nem nyújtanak semmilyen implementációt. Egy osztály csak egy absztrakt osztálytól örökölhet, de tetszőleges számú interfészt implementálhat.
Típusellenőrzés
A programozásban nagyon fontos biztosnak lenni abban, hogy az adatok, amelyekkel dolgozunk, a megfelelő típusúak. PHP-ban vannak eszközeink, amelyek ezt biztosítják számunkra. Annak ellenőrzését, hogy az adatok megfelelő típusúak-e, “típusellenőrzésnek” nevezzük.
Típusok, amelyekkel PHP-ban találkozhatunk:
- Alaptípusok: Ide tartoznak az
int
(egész számok),float
(lebegőpontos számok),bool
(logikai értékek),string
(karakterláncok),array
(tömbök) ésnull
. - Osztályok: Ha azt szeretnénk, hogy az érték egy specifikus osztály példánya legyen.
- Interfészek: Metódusok halmazát definiálja, amelyeket egy osztálynak implementálnia kell. Az interfésznek megfelelő értéknek rendelkeznie kell ezekkel a metódusokkal.
- Vegyes típusok: Meghatározhatjuk, hogy egy változó több megengedett típussal is rendelkezhet.
- Void: Ez a speciális típus azt jelzi, hogy egy függvény vagy metódus nem ad vissza értéket.
Nézzük meg, hogyan módosíthatjuk a kódot, hogy tartalmazza a típusokat:
class Szemely
{
private int $kor;
public function __construct(int $kor)
{
$this->kor = $kor;
}
public function korKiirasa(): void
{
echo "Ez a személy {$this->kor} éves.";
}
}
/**
* Függvény, amely egy Szemely osztály objektumát fogadja el és kiírja a személy korát.
*/
function szemelyKoranakKiirasa(Szemely $szemely): void
{
$szemely->korKiirasa();
}
Ezzel a módszerrel biztosítottuk, hogy a kódunk a megfelelő típusú adatokat várja és dolgozza fel, ami segít megelőzni a potenciális hibákat.
Néhány típust nem lehet közvetlenül PHP-ban leírni. Ebben az esetben a phpDoc kommentben adjuk meg, ami a PHP kód
dokumentálásának szabványos formátuma, /**
-el kezdődik és */
-el végződik. Lehetővé teszi
osztályok, metódusok stb. leírásának hozzáadását. És komplex típusok megadását is ún. annotációk @var
,
@param
és @return
segítségével. Ezeket a típusokat aztán a statikus kódelemző eszközök
használják, de maga a PHP nem ellenőrzi őket.
class Lista
{
/** @var array<Szemely> a jelölés azt mondja, hogy Szemely objektumok tömbjéről van szó */
private array $szemelyek = [];
public function szemelyHozzaadasa(Szemely $szemely): void
{
$this->szemelyek[] = $szemely;
}
}
Összehasonlítás és azonosság
PHP-ban kétféleképpen hasonlíthatunk össze objektumokat:
- Érték szerinti összehasonlítás
==
: Ellenőrzi, hogy az objektumok azonos osztályúak-e és azonos értékekkel rendelkeznek-e a property-jeikben. - Azonosság
===
: Ellenőrzi, hogy ugyanarról az objektumpéldányról van-e szó.
class Auto
{
public string $marka;
public function __construct(string $marka)
{
$this->marka = $marka;
}
}
$auto1 = new Auto('Skoda');
$auto2 = new Auto('Skoda');
$auto3 = $auto1;
var_dump($auto1 == $auto2); // true, mert azonos értékűek
var_dump($auto1 === $auto2); // false, mert nem ugyanaz a példány
var_dump($auto1 === $auto3); // true, mert $auto3 ugyanaz a példány, mint $auto1
Az instanceof
operátor
Az instanceof
operátor lehetővé teszi annak megállapítását, hogy egy adott objektum egy bizonyos osztály
példánya-e, ennek az osztálynak a leszármazottja-e, vagy implementál-e egy bizonyos interfészt.
Képzeljük el, hogy van egy Szemely
osztályunk és egy másik Diak
osztályunk, amely a
Szemely
osztály leszármazottja:
class Szemely
{
private int $kor;
public function __construct(int $kor)
{
$this->kor = $kor;
}
}
class Diak extends Szemely
{
private string $szak;
public function __construct(int $kor, string $szak)
{
parent::__construct($kor);
$this->szak = $szak;
}
}
$diak = new Diak(20, 'Informatika');
// Ellenőrzés, hogy $diak a Diak osztály példánya-e
var_dump($diak instanceof Diak); // Kimenet: bool(true)
// Ellenőrzés, hogy $diak a Szemely osztály példánya-e (mivel a Diak a Szemely leszármazottja)
var_dump($diak instanceof Szemely); // Kimenet: bool(true)
A kimenetekből látható, hogy a $diak
objektum egyszerre mindkét osztály – Diak
és
Szemely
– példányának tekintendő.
Fluent Interfészek
A “Fluent Interface” (magyarul “Fluent Interfész”) egy technika az OOP-ban, amely lehetővé teszi a metódusok láncolását egyetlen hívásban. Ez gyakran egyszerűsíti és átláthatóbbá teszi a kódot.
A fluent interfész kulcsfontosságú eleme, hogy a lánc minden metódusa hivatkozást ad vissza az aktuális objektumra. Ezt
úgy érjük el, hogy a metódus végén return $this;
-t használunk. Ezt a programozási stílust gyakran
“settereknek” nevezett metódusokkal társítják, amelyek az objektum property-jeinek értékeit állítják be.
Mutassuk be, hogyan nézhet ki egy fluent interfész egy e-mail küldési példán:
public function uzenetKuldese()
{
$email = new Email;
$email->setFelado('sender@example.com')
->setCimzett('admin@example.com')
->setUzenet('Hello, this is a message.')
->kuldes();
}
Ebben a példában a setFelado()
, setCimzett()
és setUzenet()
metódusok a megfelelő
értékek (feladó, címzett, üzenet tartalma) beállítására szolgálnak. Miután beállítottuk mindegyik értéket, a
metódusok visszaadják az aktuális objektumot ($email
), ami lehetővé teszi, hogy egy másik metódust láncoljunk
utána. Végül meghívjuk a kuldes()
metódust, amely ténylegesen elküldi az e-mailt.
A fluent interfészeknek köszönhetően olyan kódot írhatunk, amely intuitív és könnyen olvasható.
Másolás a clone
segítségével
PHP-ban létrehozhatunk egy objektum másolatát a clone
operátor segítségével. Ezzel a módszerrel egy új,
azonos tartalmú példányt kapunk.
Ha az objektum másolásakor módosítani kell néhány property-jét, definiálhatunk egy speciális __clone()
metódust az osztályban. Ez a metódus automatikusan meghívódik, amikor az objektumot klónozzák.
class Barany
{
public string $nev;
public function __construct(string $nev)
{
$this->nev = $nev;
}
public function __clone()
{
$this->nev = 'Klón ' . $this->nev;
}
}
$eredeti = new Barany('Dolly');
echo $eredeti->nev . "\n"; // Kiírja: Dolly
$klon = clone $eredeti;
echo $klon->nev . "\n"; // Kiírja: Klón Dolly
Ebben a példában van egy Barany
osztályunk egy $nev
property-vel. Amikor klónozzuk ennek az
osztálynak egy példányát, a __clone()
metódus gondoskodik arról, hogy a klónozott bárány neve “Klón”
előtagot kapjon.
Traitek
A traitek PHP-ban olyan eszközök, amelyek lehetővé teszik metódusok, property-k és konstansok megosztását osztályok között, és megakadályozzák a kódduplikációt. Úgy képzelhetjük el őket, mint egy “másolás és beillesztés” (Ctrl-C és Ctrl-V) mechanizmust, ahol a trait tartalma “beillesztődik” az osztályokba. Ez lehetővé teszi a kód újrafelhasználását anélkül, hogy bonyolult osztályhierarchiákat kellene létrehozni.
Nézzünk egy egyszerű példát a traitek használatára PHP-ban:
trait Dudalas
{
public function dudal()
{
echo 'Bip bip!';
}
}
class Auto
{
use Dudalas;
}
class Teherauto
{
use Dudalas;
}
$auto = new Auto;
$auto->dudal(); // Kiírja 'Bip bip!'
$teherauto = new Teherauto;
$teherauto->dudal(); // Szintén kiírja 'Bip bip!'
Ebben a példában van egy Dudalas
nevű traitünk, amely egy dudal()
metódust tartalmaz. Ezután
van két osztályunk: Auto
és Teherauto
, amelyek mindketten használják a Dudalas
traitet. Ennek köszönhetően mindkét osztály “rendelkezik” a dudal()
metódussal, és meghívhatjuk azt
mindkét osztály objektumain.
A traitek lehetővé teszik a kód egyszerű és hatékony megosztását az osztályok között. Eközben nem lépnek be az
öröklődési hierarchiába, azaz $auto instanceof Dudalas
false
értéket ad vissza.
Kivételek
A kivételek az OOP-ban lehetővé teszik számunkra, hogy elegánsan kezeljük a hibákat és a váratlan helyzeteket a kódunkban. Ezek olyan objektumok, amelyek információt hordoznak a hibáról vagy a szokatlan helyzetről.
PHP-ban van egy beépített Exception
osztályunk, amely az összes kivétel alapjául szolgál. Több metódusa
van, amelyek lehetővé teszik számunkra, hogy több információt kapjunk a kivételről, például a hibaüzenetet, a fájlt
és a sort, ahol a hiba történt, stb.
Amikor hiba történik a kódban, “dobhatunk” egy kivételt a throw
kulcsszó segítségével.
function osztas(float $a, float $b): float
{
if ($b === 0.0) { // Use float comparison
throw new Exception('Nullával való osztás!');
}
return $a / $b;
}
Amikor az osztas()
függvény nullát kap második argumentumként, kivételt dob a
'Nullával való osztás!'
hibaüzenettel. Annak megakadályozására, hogy a program leálljon a kivétel
dobásakor, elkapjuk azt egy try/catch
blokkban:
try {
echo osztas(10, 0);
} catch (Exception $e) {
echo 'Kivétel elkapva: '. $e->getMessage();
}
A kódot, amely kivételt dobhat, egy try
blokkba csomagoljuk. Ha kivétel dobódik, a kód végrehajtása
átkerül a catch
blokkba, ahol feldolgozhatjuk a kivételt (pl. kiírhatjuk a hibaüzenetet).
A try
és catch
blokkok után hozzáadhatunk egy opcionális finally
blokkot, amely
mindig lefut, függetlenül attól, hogy dobódott-e kivétel vagy sem (még akkor is, ha a try
vagy
catch
blokkban return
, break
vagy continue
utasítást használunk):
try {
echo osztas(10, 0);
} catch (Exception $e) {
echo 'Kivétel elkapva: '. $e->getMessage();
} finally {
// Kód, amely mindig lefut, függetlenül attól, hogy dobódott-e kivétel
}
Létrehozhatunk saját kivételosztályokat (hierarchiát) is, amelyek az Exception osztálytól örökölnek. Példaként képzeljünk el egy egyszerű banki alkalmazást, amely lehetővé teszi befizetések és kifizetések végrehajtását:
class BankiKivetel extends Exception {}
class ElegtetlenFedezetKivetel extends BankiKivetel {}
class LimitTullepesKivetel extends BankiKivetel {}
class Bankszamla
{
private int $egyenleg = 0;
private int $napiLimit = 1000;
public function befizet(int $osszeg): int
{
$this->egyenleg += $osszeg;
return $this->egyenleg;
}
public function kifizet(int $osszeg): int
{
if ($osszeg > $this->egyenleg) {
throw new ElegtetlenFedezetKivetel('Nincs elegendő fedezet a számlán.');
}
if ($osszeg > $this->napiLimit) {
throw new LimitTullepesKivetel('Túllépték a napi kifizetési limitet.');
}
$this->egyenleg -= $osszeg;
return $this->egyenleg;
}
}
Egy try
blokkhoz több catch
blokkot is megadhatunk, ha különböző típusú kivételekre
számítunk.
$szamla = new Bankszamla;
$szamla->befizet(500);
try {
$szamla->kifizet(1500);
} catch (LimitTullepesKivetel $e) {
echo $e->getMessage();
} catch (ElegtetlenFedezetKivetel $e) {
echo $e->getMessage();
} catch (BankiKivetel $e) {
echo 'Hiba történt a művelet végrehajtása során.';
}
Ebben a példában fontos megjegyezni a catch
blokkok sorrendjét. Mivel minden kivétel a
BankiKivetel
-től örököl, ha ezt a blokkot tennénk elsőnek, az összes kivételt elkapná, anélkül, hogy a
kód eljutna a következő catch
blokkokhoz. Ezért fontos, hogy a specifikusabb kivételek (azaz azok, amelyek
másoktól örökölnek) a catch
blokkban magasabban legyenek a sorrendben, mint a szülő kivételeik.
Iteráció
PHP-ban objektumokon iterálhatunk a foreach
ciklus segítségével, hasonlóan ahhoz, ahogy tömbökön
iterálunk. Ahhoz, hogy ez működjön, az objektumnak implementálnia kell egy speciális interfészt.
Az első lehetőség az Iterator
interfész implementálása, amelynek metódusai a current()
(aktuális értéket adja vissza), key()
(kulcsot adja vissza), next()
(következő értékre lép),
rewind()
(kezdetre lép) és valid()
(ellenőrzi, hogy még nem értünk-e a végére).
A második lehetőség az IteratorAggregate
interfész implementálása, amelynek csak egyetlen
getIterator()
metódusa van. Ez vagy egy helyettesítő objektumot ad vissza, amely biztosítja az iterációt, vagy
lehet egy generátor, ami egy speciális függvény, amelyben a yield
-et használjuk a kulcsok és értékek
fokozatos visszaadására:
class Szemely
{
public function __construct(
public int $kor,
) {
}
}
class Lista implements IteratorAggregate
{
private array $szemelyek = [];
public function szemelyHozzaadasa(Szemely $szemely): void
{
$this->szemelyek[] = $szemely;
}
public function getIterator(): Generator
{
foreach ($this->szemelyek as $szemely) {
yield $szemely;
}
}
}
$lista = new Lista;
$lista->szemelyHozzaadasa(new Szemely(30));
$lista->szemelyHozzaadasa(new Szemely(25));
foreach ($lista as $szemely) {
echo "Kor: {$szemely->kor} év \n";
}
Helyes gyakorlatok
Miután elsajátította az objektumorientált programozás alapelveit, fontos a helyes OOP gyakorlatokra összpontosítani. Ezek segítenek olyan kódot írni, amely nemcsak funkcionális, hanem olvasható, érthető és könnyen karbantartható is.
- Felelősségek szétválasztása (Separation of Concerns): Minden osztálynak világosan meghatározott felelősséggel kell rendelkeznie, és csak egy fő feladatot kell megoldania. Ha egy osztály túl sok mindent csinál, érdemes lehet kisebb, specializált osztályokra bontani.
- Egységbezárás (Encapsulation): Az adatokat és metódusokat a lehető legjobban el kell rejteni, és csak egy definiált interfészen keresztül szabad hozzáférni hozzájuk. Ez lehetővé teszi az osztály belső implementációjának megváltoztatását anélkül, hogy a kód többi részét befolyásolná.
- Függőséginjektálás (Dependency Injection): Ahelyett, hogy a függőségeket közvetlenül az osztályban hozná létre, azokat kívülről kellene “injektálni”. Ennek az elvnek a mélyebb megértéséhez javasoljuk a Dependency Injection fejezetek elolvasását.