Výklad základů OOP v jazyce PHP s příklady.
Co je to OOP?
V objektovém orientovaném programování jsou data a kód spojený do jedné entity - objekt. V objektu máme vlastnosti a metody. Vlastnosti obsahují data a metody plní úlohu funkce. U vlastností a metod si můžeme nastavit jejich veřejnost - mohou být veřejné, chráněné nebo až soukromé. Každá z těchto typů se liší jak můžeme k dané vlastnosti / metody přistupovat. Třída je šablona objektu a popisuje jaké vlastnosti a metody bude obsahovat.
Základní syntaxe
Třídy definujeme pomocí syntaxe class Nazev
. Následně deklarujeme vlastnosti a nakonec metody. Viz následující příklad, kde je to vše popsáno:
class Zamestnanec
{
private $meno = 'Utajený'; // deklarovali sme vlastnosť $meno a určili jej, že je súkromna. Taktiež sme nastavili začiatočnú hodnotu a to Utajený, teda nevieme o akého zamestnanca ide
public function nastavMeno($meno) // vytvárame verejnú metódy na nastavenie mena
{
$this->meno = $meno; // s vlastnosťami nášej triedy pracujeme $this->nazovVlastnosti
}
public function ukazMeno() // netreba nám zadávať žiadne parametre, slúži iba na vrátenie hodnoty
{
return $this->meno;
}
}
Vytvořili jsme třídu Zamestnanec
, která má 1 soukromou vlastnost $meno
a 2 veřejné metody nastavMeno()
aukazMeno()
. Následně si podívejme, jak s tímto objektem pracovat v praxi:
$zamestnanec = new Zamestnanec; // s príkazom new vytvárame novú inštanciu objektu Meno
echo $zamestnanec->ukazMeno(); // keďže sme meno nenastavili, tak output bude východzia hodnota vlastnosti $meno: Utajený
$meno = 'Janko Mrkvička';
$zamestnanec->nastavMeno($meno); // nastavili sme vlasnosť $meno na novú hodnotu
echo $zamestnanec->ukazMeno(); // output: Janko Mrkvička
Co je to $this
? Je to speciální proměnná, která slouží jako zástupce dané instanci třídy, resp. objektu. Tím se odkazujeme, že pracujeme s vlastnostmi a metodami jedné instanci a neovlivňuje data stejných vlastností / metod jiné instanci stejné třídy.
Konstruktor
Konstruktor je speciální typ metody, který se spouští ihned po vyvolání třídy. Hodnoty, tedy parametry vkládáme hned na začátku:
$objekt = new Trieda($parametre);
Tedy pokud máte objekt, který již má co provést (např. Objekt pro spojení s databázovým serverem tak jako první parametry můžete vložit přihl. Údaje namísto toho, abyste ihned po vyvolání objektu vyvolávaly metodu na spojení).
V PHP 4 se v OOP používal jako konstruktor metoda, která mála stejný název jako název třídy. S tím se setkáme iv jiných prog. jazycích (např. Java). V PHP 5 byla zavedena speciální vyhrazený název metody a tím je __construct()
. Pracuje stejně jako běžná metoda, jen se spouští automaticky při vyvolávání objektu. Ukažme si to v následujícím příkladu:
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function ukazMeno()
{
return $this->meno;
}
}
Vytvořili jsme si třídu Zamestnanec
a konstruktor. Teď už při nastavení jména nepotřebujeme mít metodu navíc, stačí když to vložíme hned při vyvolávání:
$janko = new Zamestnanec('Janko Mrkvička'); // Janko sa stáva novým zamestnancom
echo $janko->ukazMeno(); // output: Janko Mrkvička
Konstruktor se využívá téměř vždy pokud je to nutné. Konstruktor může mít i roli přijímání parametrů, tedy instancí jiných objektů aby mohl daná třída s nimi pracovat.
Destruktor
Destruktor se na rozdíl od konstruktoru liší tím, že nespouští při vyvolávání objektu, ale při jeho "zničení". Zničit ho můžeme tak, že využijeme funkci unset()
, která vymaže proměnnou z paměti nebo hodnotu proměnné nastavíme na null
. Může se v praxi využít např. na uzavření spojení s databázovým systémem (MySQL, ...). My si ukážeme trochu jednodušší tvar, kde při zničení objektu vypíšeme zprávu, že práci s Jankem Mrkvička již skončily:
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function __destruct()
{
echo $this->meno . ' opustil náš tím';
}
}
Třída je deklarována a můžeme si objekt Zamestnanec
, který se bude týkat našeho Janka zničit, protože už přestal spolupracovat a odešel:
$janko = new Zamestnanec('Janko Mrkvička'); // vytvoríme si zamestnanca Janka Mrkvičku
unset($janko); // Janko odchádza, daný objekt už nepotrebujeme
Veřejné, chráněné a soukromé vlastnosti
Vlastnost může mít 3 typy přístupu - veřejný, chraň a soukromý. Každý se liší samozřejmě trochu jinak, všechno to máme popsáno v následující tabulce:
modifikátor | popis |
---|---|
public |
můžeme k ní přistupovat a pracovat odkudkoliv |
protected |
můžeme k ní přistupovat a pracovat pouze v dotyčné třídě a potomkům dané třídy |
private |
můžeme k ní přistupovat a pracovat pouze v dotyčné třídě |
Typ přístupu určujeme tak, že před názvem vlastnosti napíšeme modifikátor ( private $vlastnost
, u public
vlastností se nic nestane, pokud to napíšeme aniž protože je to výchozí hodnota vlastnosti, pokud není určeno jinak.). Ukažme si všechny tyto typy v jednoduchém příkladu:
class Trieda
{
public $verejnaVlastnost; // to isté ako iba $verejnaVlastnost bez public
protected $chranenaVlastnost = 'Víta vás chránená vlastnosť';
private $sukromnaVlastnost = 'A na záver vás víta súkromná vlastnosť';
public function tlacSukromnu()
{
print $this->sukromnaVlastnost;
}
}
class PodTrieda extends Trieda
{
public function tlacChranenu()
{
print($this->chranenaVlastnost);
}
public function tlacSukromnu()
{
print($this->sukromnaVlastnost);
}
}
$trieda = new Trieda;
$podtrieda = new Podtrieda;
$trieda->verejnaVlastnost = 'Toto je OK';
print($trieda->verejnaVlastnost); // output: Toto je OK
$trieda->chranenaVlastnost = 'Toto už nebude OK';
print($trieda->chranenaVlastnost); // output: Fatal error: Cannot access protected property Trieda::$chranenaVlastnost
$podtrieda->tlacChranenu(); // output: Víta vás chránená vlastnosť
$trieda->sukromnaVlastnost = 'Toto už duplom nebude OK';
print($trieda->sukromnaVlastnost); // output: Fatal error: Cannot access private property Trieda::$sukromnaVlastnost
$podtrieda->tlacSukromnu(); // output bude prázdna hodnota, lebo $sukromnaVlastnost sa nededí
$trieda->tlacSukromnu(); // output: A na záver vás víta súkromná vlastnosť
No co nám tím chtěl autor říci. Vytvořili jsme si základní třídu Trieda
a třídu PodTrieda
, která je dědicem první třídy. O dědičnosti si povíme později, takže v tomto příkladu toto rozebírat nebudeme. Zatím nám stačí a to, že třída PodTrieda
má zdědit vlastnosti a metody z její rodiče.
Dřív jsme si vyzkoušeli veřejnou vlastnost, která funguje tak jako chceme - můžeme s ní pracovat mimo těchto tříd bez problémově.
Následně zkoušíme chráněnou vlastnost, která s námi mimo tříd již komunikovat nechce a dostaneme PHP error. Přistupovat k ní ale můžeme z potomka a tím je třída PodTrieda
, jak jsme si již na začátku zmínili. Tam se nám již obsah této vlastnosti pěkně vypsal.
A na konec nám zůstala soukromá vlastnost, kterou již nebylo možné ani vyvolat z potomka, protože při dědění se tato vlastnost na potomka nepřesunula a proto nám při výpisu zůstala prázdná hodnota. Ale když jsme to zkusili přímo přes rodiče, vlastnost se nám již vypsala tak, jak jsme očekávali.
Veřejné, chráněny a soukromé metody
Tak jako i vlastnosti, známe metody podle možnosti přístupu - veřejné , chráněné a soukromé . Také jako iu vlastností, představme si tabulku, kde máme tyto modifikátory popsány:
modifikátor | popis |
---|---|
public |
můžeme je vyvolávat odkudkoliv |
protected |
můžeme je vyvolávat pouze v dotyčné třídě a potomkům třídy |
private |
můžeme je vyvolávat pouze v dotyčné třídě |
Využití je stejné, proto si nemusíme dělat stejně dlouhý příklad s dlouhým popisem, ale jen na ukázku, že to tak funguje:
class Trieda
{
public function verejnaMetoda()
{
print('Verejná metóda pozdravuje');
}
protected function chranenaMetoda()
{
print('Aj metóda s ochrankou posiela pozdravy');
}
private function sukromnaMetoda()
{
print('Nikomu sa nepoddám, len svojej triede');
}
public function vykonajSukromnu()
{
$this->sukromnaMetoda();
}
}
class PodTrieda extends Trieda
{
public function vykonajChranenu()
{
$this->chranenaMetoda();
}
public function vykonajSukromnu()
{
$this->sukromnaMetoda();
}
}
$trieda = new Trieda;
$podtrieda = new Podtrieda;
$trieda->verejnaMetoda(); // output: Verejná metóda pozdravuje
$trieda->chranenaMetoda(); // output: Fatal error: Call to protected method Trieda::chranenaMetoda() from context ''
$podtrieda->vykonajChranenu(); // output: Aj metóda s ochrankou posiela pozdravy
$trieda->sukromnaMetoda(); // to uz nam zasa duplom nepojde
$podtrieda->vykonajSukromnu(); // output: Fatal error: Call to private method Trieda::sukromnaMetoda() from context 'PodTrieda'
$trieda->vykonajSukromnu(); // output: Nikomu sa nepoddám, len svojej triede
Opět to funguje stejně. Veřejná metoda je přístupná odkudkoliv, chráněna pouze z příslušné třídy a potomka a soukromá jen z dané příslušné třídy.
Statické vlastnosti a metody
Statické vlastnosti
Statické vlastnosti jsou takové, které nepatří objektu (tedy nějaké instanci), ale třídě celkově. To znamená, že není ovlivňována nějakým objektem a vždy má stejnou hodnotu, i když používáme N objektů najednou. Statická vlastnost je globální (veřejná - public) a proto je možné k ní přistupovat odkudkoliv. Tuto vlastnost nevyvoláváme znakem $this->
ale zadáním slova self a dvou dvojteček self::
. Statickou vlastnost deklarujeme klíčovým slovem static
:
class Trieda
{
static $text = "Ahoj!"; // deklarujeme statickú vlastnosť kľúčovým slovom static
function vratText()
{
return self::$text; // self::[statická vlastnosť]
}
}
$trieda = new Trieda;
print $trieda->vratText(); // vyvolávame funkciu, ktoré vráti obsah statickej vlastnosti
print "\n";
print Trieda::$text; // ukážka, že je možné na statickú vlastnosť pristupovať aj zvonku. Toto platí aj bez toho, aby sme museli vytvárať inštanciu triedy.
Takto můžeme pracovat se statickými vlastnostmi, pokud potřebujeme pracovat s hodnotou, která se nepotřebuje měnit vzhledem k daný objekt. Obsah statické vlastnosti změnit můžeme a nová hodnota se projeví všude tam, kde se tato vlastnost využívá:
// ... kód predtým
Trieda::$text = 'Iný text';
print Trieda::$text;
print "\n" ;
print $trieda->vratText();
Statické metody
Stejně jako i vlastnosti, v PHP máme i statické metody. Jsou to metody, které jsou součástí třídy, ale není spojena s žádnou instancí třídy. Statickou metodou jakož i vlastnosti nepoužívají speciální proměnnou $this
(zástupce objektu, resp. Dané instance třídy) ale identifikátor self
, který je zástupcem třídy. Volat statické metody můžeme i bez toho, abychom museli vytvářet nějaký nový objekt. Podívejme si následující příklad:
class Trieda
{
static function vypisAhoj() // deklarujeme statickú metódu
{
print('Ahoj svet!');
}
public function vyvolajStaticku()
{
return self::vypisAhoj(); // vyvolávame pomocou self::metóda()
}
}
Trieda::vypisAhoj(); // vyvolávame bez toho, aby sme vytvárali nový objekt
print("\n");
$trieda = new Trieda(); // vytvorili sme si objekt na vyvolanie druhej metódy
$trieda->vyvolajStaticku(); // vyvolávame statickú metódu z verejnej metódy daného objektu
Funguje to jako u vlastností, vyvolaly jsme statickou metodu bez vytváření instance třídy, pak jsme si tu instanci vytvořili abychom vyvolali veřejnou metodu, která využívá statickou na výpis textu.
Konstanty
Konstanty v OOP hrají stejnou roli, jako v běžném kódu. Doporučují se psát velkými písmeny a platí tam stejná pravidla při názvu konstanty, jen se trošku jinak deklarují. Využíváme k tomu klíčové slovíčku const
a pak následuje název konstanty a její hodnota. Pak při vyvolávání používáme to, co iu statických vlastnosti a to identifikátor self
, protože konstanta patří třídě a ne objektu. Jako je zvykem, i nyní nás na to čeká příklad:
class Trieda
{
const OVOCIE = 'jablko';
public function __construct()
{
print('Isaac Newton vytvoril teóriu gravitácie vďaka niečomu, čo mu spadlo na hlavu zo stromu. Jednalo sa o ' . self::OVOCIE);
}
}
print('Moje obľúbené ovocie je ' . Trieda::OVOCIE); // vyvolávame konštantu bez toho, aby sme vytvárali inštanciu triedy
print("\n");
new Trieda(); // vytvárame novú inštanciu objektu, nepotrebujeme ju ani ukladať do premennej, keďže sa vykoná ihneď.
Abstraktní metody a třídy
V PHP budeme určitě někdy potřebovat, aby určité metody nějaké třídy vykonávali až její potomci. K tomu nám právě slouží abstraktní metody . Představme si, že chceme vytvořit třídu Utvar
, která bude mít metodu pocetStran()
. Následně vytvořit potomků jako Stvorec
, Trojuholnik
a Patuholnik
. Je zřejmě jasné, že nemůžeme již v dané metodě Utvar
vypsat počty stran různých útvarů. K tomu si vytvoříme, že metoda pocetStran()
bude abstraktní , tedy bude vědět, že má očekávat její vyplnění až od jejího potomků. Dřív, ale musíme určit celou třídu aku abstraktní , abychom PHP řekli, že daná třída nebude mít kompletní funkčnost a bude sloužit jako bázová třída, od které se bude odvozovat. U abstraktní třídě nemůžeme vytvářet její instance, jedině u její potomků.
abstract class Utvar
{
abstract protected function pocetStran(); // ďalej nerozpisujeme!
public function vypisPocetStran()
{
print($this->pocetStran());
}
}
class Stvorec extends Utvar
{
protected function pocetStran()
{
return 4;
}
}
class Trojuholnik extends Utvar
{
protected function pocetStran()
{
return 3;
}
}
class Patuholnik extends Utvar
{
protected function pocetStran()
{
return 5;
}
}
$stvorec = new Stvorec;
$stvorec->vypisPocetStran(); // output: 4
$trojuholnik = new Trojuholnik;
$trojuholnik->vypisPocetStran(); // output: 3
$patuholnik = new Patuholnik;
$patuholnik->vypisPocetStran(); // output: 5
$utvar = new Utvar; // output: Fatal error: Cannot instantiate abstract class Utvar
Jako v tomto příkladu můžeme vidět, že jsme si vytvořili abstraktní třídu Útvar s abstratnou metodou pocetStran()
a její potomky. Každý vrátil jinou hodnotu počty stran a pokud jsme se pokoušeli vytvořit instanci abstraktní třídy, PHP nám odpovědělo svým typickým Fatal error. Jak jste si jinak mohli všimnout, je to, že jsme použili modifikátor protected
a ne public
a to z toho důvodu, že pocetStran()
nás veřejně zajímat nemusí, my jsme chtěli využít jen metodu vypisPocetStran()
, který se odvolá na tu chráněnou metodu.
Identifikátory self:: a parent::
V PHP máme 2 speciální identifikátory, které zároveň zastupují již předem rezerovaný názvy tříd. Jedná se o:
self::
- Odkazuje na aktuální třídu a používá se na manipulaci se statickými členy, metodami a konstantamiparent::
- Odkazuje se na bázovou třídu (tedy rodiče) a často se využívá na volání konstruktoru nebo metod dané třídy
Sice jsme již identifikátor self:: použili v minulých příkladech, ale ani teď si to neukážeme bez příkladu:
class Predok
{
const NAZOV = 'Predok';
public function __construct()
{
print("Názov tejto triedy je " . self::NAZOV . "\n");
}
}
class Potomok extends Predok
{
const NAZOV = 'Potomok'; // prepíšeme konštantu tým, že ju deklarujeme znova s inou hodnotou
public function __construct()
{
parent::__construct(); // vyvolávame konštruktor predka
print("Názov tejto triedy je " . self::NAZOV . "\n");
}
}
$potomok = new Potomok(); // vytvárame objekt potomka
Co jsme vlastně udělali. Deklarovali jsme si třídy Predok
a Potomok
, kde třída Potomok
je dědicem třídy Predok
. U konstruktoru potomka jsme se odvolali na konstruktor rodiče a ještě jsme si přidali vypsání textu naší třídy. Samozřejmě, na vypsání třídy můžeme použít speciální konstantu __CLASS__
, kterou jsme si již zmínili v I. části tutoriálu.
Operátor instanceof
Operátor instanceof
je považován jako logický binární operátor a využívá se zjištění, zda daná instance třídy patří dané třídě. Nechme si to ukázat na následujícím příkladu:
class Stvoruholnik // vytvaráme všeobecnú triedu Stvoruholnik
{
public $nazov = __CLASS__;
}
class Stvorec extends Stvoruholnik // trieda Stvorec bude potomkom triedy Stvoruholnik
{
public $nazov = __CLASS__;
}
class Kruh // vytvoríme si triedu Kruh, ktorá určite nemôže byť štvoruholníkom
{
public $nazov = __CLASS__;
}
function overitStvoruholnik($object)
{
// pýtame sa, či daný objekt je typu triedy Stvoruholnik, platí to aj pre dedičov
if($object instanceof Stvoruholnik) {
print($object->nazov . ' je štvoruholník');
}
else {
print($object->nazov . ' nie je štvoruholník');
}
}
overitStvoruholnik(new Stvorec);
print("\n");
overitStvoruholnik(new Kruh);
Jak to je již v kódu popsáno, vytvořili jsme si třídu Stvorec
, která je potomkem třídy Stvoruholnik
a třídu Kruh
, která předka nemá. Následně jsme pomocí operátora instanceof
porovnali, zda daná třída je nebo není objektem třídy Stvoruholnik
.
Polymorfismus
Polymorfismus je v OOP jedna z nejdůležitějších věcí. Tento mechanismus na principu dědičnosti, kde pracujeme s potomky a vyvoláváme stejnou metodu u různých typů potomků. Sice to může znít složitě, ale není to tak. Uvažujme následující příklad:
class Riaditel
{
public function podpisZmluvy()
{
print("Podpíšem tie otravné zmluvy, len na to, aby som bol v reklame na Hviezdnu rotu.\n");
}
}
class Sekretarka
{
public function nachystajZmluvy()
{
print("Nachystám tie zmluvy tomu odpornému a nonstop spitému šéfovi.\n");
}
}
class Spevacka
{
public function zaspievaj()
{
print("Zaspievam pieseň, aby som bola TOP v Hviezdnej rote.\n");
}
}
class Upratovacka
{
public function uprac()
{
print("Upracem tú podlahu, ktrú nonstop špiní ten 2,6 promile Kápo.\n");
}
}
function vykonajSvojuUlohu($osoba)
{
if($osoba instanceof Riaditel) {
$osoba->podpisZmluvy();
}
elseif($osoba instanceof Sekretarka) {
$osoba->nachystajZmluvy();
}
elseif($osoba instanceof Spevacka) {
$osoba->zaspievaj();
}
elseif($osoba instanceof Upratovacka) {
$osoba->uprac();
}
else {
print('Táto osoba tu nepracuje.');
}
}
vykonajSvojuUlohu(new Sekretarka);
vykonajSvojuUlohu(new Riaditel);
vykonajSvojuUlohu(new Spevacka);
vykonajSvojuUlohu(new Upratovacka);
Výstup:
Nachystám tie zmluvy tomu odpornému a nonstop spitému šéfovi.
Podpíšem tie otravné zmluvy, len na to, aby som bol v reklame na Hviezdnu rotu.
Zaspievam pieseň, aby som bola TOP v Hviezdnej rote.
Upracem tú podlahu, ktrú nonstop špiní ten 2,6 promile Kápo.
Ale jak vidíte, není to nejlepší příklad a kdybychom měli rozšířit tuto fimu na Hvězdnou rotu, bylo by to dost nepřehledné a přecpané if
-mi. Na to tu máme polymorfismus . Podívejme se na to, kdybychom provedli pár změn:
class Zamestnanec
{
public function vykonajUlohu()
{
print("Error: túto metódu môžu vyvolať len zamestnanci!");
}
}
class Riaditel extends Zamestnanec
{
public function vykonajUlohu()
{
print("Podpíšem tie otravné zmluvy, len na to, aby som bol v reklame na Hviezdnu rotu.\n");
}
}
class Sekretarka extends Zamestnanec
{
public function vykonajUlohu()
{
print("Nachystám tie zmluvy tomu odpornému a nonstop spitému šéfovi.\n");
}
}
class Spevacka extends Zamestnanec
{
public function vykonajUlohu()
{
print("Zaspievam pieseň, aby som bola TOP v Hviezdnej rote.\n");
}
}
class Upratovacka extends Zamestnanec
{
public function vykonajUlohu()
{
print("Upracem tú podlahu, ktrú nonstop špiní ten 2,6 promile Kápo.\n");
}
}
function vykonajSvojuUlohu($osoba)
{
if($osoba instanceof Zamestnanec) {
$osoba->vykonajUlohu();
}
else {
print('Táto osoba tu nepracuje.');
}
}
vykonajSvojuUlohu(new Sekretarka);
vykonajSvojuUlohu(new Riaditel);
vykonajSvojuUlohu(new Spevacka);
vykonajSvojuUlohu(new Upratovacka);
Output bude stejný jako minule samozřejmě. Nyní můžete vidět, že jsme si vytvořili třídu Zamestnanec
, který obsahuje metodu vykonajUlohu()
, kterou když vyvoláme přímo z této třídy, vyhodí nám to chybu, kterou jsme si sami připravili. Nyní již máme tyto třídy odvozené od zaměstnance a proto nám tento jednoduchý kód:
if($osoba instanceof Zamestnanec)
vynahradí všechny if
-y, které jsme museli použít podle toho, jaké třídy daný objekt je. To je vše, vidíte není v tom žádná věda jak se jinak na první pohled může zdát.
Konečné metody
Viděli jsme, že v PHP je možné metody pomocí dědění přepsat, aby obsahovaly nové hodnoty nebo vykonávali něco jiného pod stejným názvem (např. Předchozí příklad). Někdy ale chcete mít jistotu, že tato metoda již nepůjde nijak jednou přepsat nebo upravit. K tomu si PHP připravil konečné metody, které již nejdou upravovat nebo přepisovat a deklarují se klíčovým slovem final
. Následující příklad znázorňuje to, že opravdu již není možné měnit tělo metody:
class Pocitadlo
{
protected $cislo;
final public function pocitaj()
{
return $this->cislo++
}
}
class SuperPocitadlo extends Pocitadlo
{
public function pocitaj()
{
return $this->cislo += 2;
}
}
Výstup:
Fatal error: Cannot override final method Pocitadlo::pocitaj()
Jak již bylo zmíněno, nefunguje to z důvodu, že se snažíme upravit, resp. přepsat zděděnou metodu pocitaj()
, která byla označena již jako konečná .
Konečné třídy
Konečné třídy stejně jako metody naznačují, že tělo se již nebude moci dát měnit. Používáme tak jako iu metod při deklaraci klíčové slovo final
. Proto u konečných tříd nelze vytvořit potomka. I tento příklad vyvolá PHP error:
final class KonecnaTrieda
{
// ...
}
class SuperTrieda extends KonecnaTrieda
{
// ...
}
Výstup:
Fatal error: Class SuperTrieda may not inherit from final class (KonecnaTrieda)
Třída KonecnaTrieda
byla deklarována jako konečná (tedy final
) a pokoušeli jsme se vytvořit třídu supertřída , která měla být potomkem třídy KonecnaTrieda
. Ale protože je konečná, PHP nám to připomněl Fatal error.
Metoda __toString ()
Pamatujete si naši třídu Zamestanec
? Pokud ne, nevadí, protože se ji pokusíme trochu zidealizoval více, než je. Podívejte se na následující příklad:
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
}
$jozko = new Zamestnanec('Jožko Vajda'); // nemyslíme našu celebritu, meno je všeobecné a na nikoho nepoukazuje!
print($jozko);
Výstup:
Catchable fatal error: Object of class Zamestnanec could not be converted to string
Co asi není podle našich představ. Jak tedy udělat, abychom při vypsání objektu Zaměstnanec dostaly jméno zaměstnaců aniž abychom vytvářeli metodu, kterou pak budeme vyvolávat? Přesně tak. Metodou __toString()
. Podívejme se nyní:
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function __toString()
{
return $this->meno;
}
}
$jozko = new Zamestnanec('Jožko Vajda'); // nemyslíme našu celebritu, meno je všeobecné a na nikoho nepoukazuje!
print($jozko);
Výstup:
Jožko Vajda
Metoda __toString()
se spustí právě tehdy, když se snažíme vypsat metodu jako string (echo, print) a to nám v tomto případě vrátilo jméno zaměstnance, tak jak jsme očekávali. V praxi se tento systém využívá často.
Rozhraní
Rozranie v PHP znamená něco jako vzor, jehož se ostatní třídy budou držet. Samozřejmě, že tyto třídy mohou využívat i další metody, ale musí v sobě zahrnout všechny metody, které to rozhraní obsahuje. Rozranie funguje podobně jako třídy může obsahovat pouze prototy funkcí (bez těla) a konštakty. Deklarují se klíčovým slovem interface
, dále následuje název tohoto rozhraní. Třídy, mohou obsahovat několik rozhraní, které se přidávají při deklaraci třídy až po klíčovém slově Extends a názvy tříd (pokud dědíme) klíčovým slovem implements
:
class MojaTrieda extends VelkaTrieda implements Rozhranie1, Rozhranie2
{
// ...
}
Příklad:
interface Zapisovac
{
const INFO = 'Informácia';
const WARNING = 'Varovanie';
const ERROR = 'Chyba!';
public function zaznamenaj(); // koniec, viac nepíšeme!
}
class Zamestnanec implements Zapisovac
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function zaznamenaj()
{
return self::INFO . ' - osoba ' . $this->meno . ' sa stal(a) novým členom nášho tímu';
}
}
class Produkt implements Zapisovac
{
private $nazov;
public function __construct($nazov)
{
$this->nazov = $nazov;
}
public function zaznamenaj()
{
return self::WARNING . ' - produkt ' . $this->nazov . ' už má menej ako 10 kusov na sklade';
}
}
class Parsovac implements Zapisovac
{
private $funkcia;
public function __construct($funkcia)
{
$this->funkcia = $funkcia;
}
public function zaznamenaj()
{
return self::ERROR . ' - funkcia ' . $this->funkcia . ' sa v našom systéme nenachádza';
}
}
// deklarujeme funkciu na zaznamenanie
function mojZaznam($objekt)
{
if($objekt instanceof Zapisovac)
{
print($objekt->zaznamenaj() . "\n");
}
else
{
print("Tento objekt nemá vzor Zapisovač!\n");
}
}
mojZaznam(new Zamestnanec('Jožko Vajda'));
mojZaznam(new Produkt('100 tipov pre dobrú manažérku'));
mojZaznam(new Parsovac('oprav_chyby'));
Výstup:
Informácia - osoba Jožko Vajda sa stal(a) novým členom nášho tímu
Varovanie - produkt 100 tipov pre dobrú manažérku už má menej ako 10 kusov na sklade
Chyba! - funkcia oprav_chyby sa v našom systéme nenachádza
Jak v tomto jednoduchém příkladu vidíte, udělali jsme si rozhraní Zapisovac
, které obsahuje 3 konstanty a 1 metodu zaznamenaj()
, která ale nesmí mít tělo (to definujeme u třídách, které toto rozhraní implementují). Vytvořili jsme si 3 třídy - Zamestnanec
, Produkt
, Parsovac
- každý z nich dělal něco jiného. Všechny ale implementovali rozhraní Zapisovac
, které vrací hodnoty v závislosti na třídě. Následně jsme si vytvořili funkci mojZaznam()
, která zjišťovala, zda daná třída obsahuje implementaci Zapisovac
a pokud ano, vyvolá metodu zaznamenaj()
s tím, že hodnotu i vypíše.
Dědičnost rozhraní
Rozhraní můžeme dědit stejně jako třídy. V tomto případě je však vícenásobné dědění (dědění z více tříd) povoleno:
interface Rozhranie extends Rozhranie2, Rozhranie3, ...
{
// ...
}
U rozhraní tak jako u tříd je třeba mít na paměti, že můžeme dědit jen takové rozhraní, které se nebudou "překrývat". To znamená, že pokud Rozhranie2
má např. stejné metody jako Rozhranie3
, způsobí to chybu. Tedy ve zkratce, každé rozhraní musí mít jinak pojmenované metody, nesmí nastat, aby se dvě nebo více metod přepisovali.
Ošetření výjimek
Ošetření výjimek je v programování dost velkým problémem. Důležité a hlavně složité je kód naprogramovat tak, aby byl připraven na všechny možné chyby, které mohou během běhu skriptu nastat. Může se jednat např. o neschopnost se připojit na databázový server, nebo prostě určitý produkt neexistuje. Jedním z nejznámějších a hlavně nejoblíbenějším vzorem ošetřování výjimek je try ... catch ... throw . Funguje to na principu, že určitý blok kódu si vložíme do struktury try
a pokud dojde k výjimce, catch
tuto výjimku zachytí a rozhodně dál, co s ní. Pak můžeme použít příkaz throw
, který danou výjimku zpracuje a rozhodne, co dál (např. Vypíše, že tento produkt se u nás již nenachází). Viz následující strukturu:
try {
// blok kódu, u ktorého môže vzniknúť nejaká výnimka
}
catch (PrvaTriedaKtoraMoznoVyvolaVynimku $prvaTeieda) {
// kód, ktorý sa vyvolá ak to bola trieda PrvaTriedaKtoraMoznoVyvolaVynimku
}
catch (DruhaTriedaKtoraMoznoVyvolaVynimku $druhaTrieda) {
// kód, ktorý sa vyvolá ak to bola trieda DruhaTriedaKtoraMoznoVyvolaVynimku
}
Jak vidíte, konstrukce try si ohraničí blok, který může vyvolat výjimku a za ní následuje N počet bloků catch ()
, která dřív najde viníka této výjimky a pokud se najde, provede se soubor příkazů, které se v tomto bloku nacházejí.
Příkaz throw
má syntaxi:
throw ;
Tento příkaz může vyvolat jen objekt, žádný řetězec nebo číslo se v úvahu nebere. V PHP se nachází již předdefinovaná třída výjimek Exception , od níž musí být odvozeny třídy všech ostatních výjimek (to znamená, že pokud si vytváří třídu na výjimky, musí být potomkem třídy Exception
). Ukažme si, jak vlastně vypadá třída Exception
:
class Exception
{
protected $message = 'Unknown exception'; // exception message
private $string; // __toString cache
protected $code = 0; // user defined exception code
protected $file; // source filename of exception
protected $line; // source line of exception
private $trace; // backtrace
private $previous; // previous exception if nested exception
public function __construct($message = null, $code = 0, Exception $previous = null);
final private function __clone(); // Inhibits cloning of exceptions.
final public function getMessage(); // message of exception
final public function getCode(); // code of exception
final public function getFile(); // source filename
final public function getLine(); // source line
final public function getTrace(); // an array of the backtrace()
final public function getPrevious(); // previous exception
final public function getTraceAsString(); // formatted string of trace
// Overrideable
public function __toString(); // formatted string for display
}
Ukažme si jednoduché vytvoření vlastní Exception
třídy, kterou využijeme k tomu, zda vkládaný objekt není náhodou typu null
:
class PrazdnaHodnotaExc extends Exception
{
public function __construct($sprava)
{
parent::__construct($sprava); // vyvoláme konštruktor triedy Exception, viac nám netreba
}
}
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function __toString()
{
return $this->meno;
}
}
function vypisZamestnanca($zamestnanec)
{
if($zamestnanec === null) {
throw new PrazdnaHodnotaExc('Vložili ste prázdny objekt.');
}
else {
print($zamestnanec . "\n");
}
}
try {
vypisZamestnanca('Janko Mrkvička');
vypisZamestnanca(null); // vyvoláme výnimku
vypisZamestnanca('Jožko Vajda');
} catch (PrazdnaHodnotaExc $e) {
print($e->getMessage()); // vypíšeme správu chyby
print("\n");
print("Chyba v súbore " . $e->getFile() . " na riadku " . $e->getLine());
} catch (Exception $e) {
// tu sa program nikdy nedostane
}
Výstup:
Janko Mrkvička
Vložili ste prázdny objekt.
Chyba v súbore ___ na riadku ___
Můžete si všimnout, že se vypsalo jméno pouze Janko Mrkvička. To proto, že když jsme vkládali po 2. jako prázdný parametr, vyhodilo to výjimku, kterou zachytil catch
a soubor příkazů se v bloku try
zastavil. Také jsme využili zděděné metody getFile()
a getLine()
na zjištění názvu souboru a čísla řádku, kde se daná výjimka vyskytla.
Funkce __autoload ()
Představte si, že máte projekt, kde používáte 3 třídy. Zřejmě byste to řešili pomocí require
, protože se jedná pouze o 3 řádky. Ale představte si, že máte projekt, kde využíváte 20 a více tříd! I tam byste se rozhodli pro require
a každou třídu psát samostatně? Asi ne. K tomu nám PHP vytvořilo nádhernou funkci __autoload()
, který za nás vloží všechny třídy, které se budeme snažit vyvolat. Jak parametr zadáváme název třídy, ale to nás zajímat ani nemusí, protože mi tuto funkci vyvolávat nebudeme, jen deklarovat. O vyvolávání se postará za nás PHP. Ukažme si to následovně:
soubor autoloader.php
function __autoload($nazov_triedy)
{
// predpokládame, že názvy súborov budú vždy malými písmenami
require_once('classes/' . strtolower($nazov_triedy) . '.php');
}
soubor classes/zamestnanec.php
class Zamestnanec
{
private $meno;
public function __construct($meno)
{
$this->meno = $meno;
}
public function __toString()
{
return $this->meno;
}
}
soubor index.php
require_once('autoloader.php'); // jeden require na autoloader a o život máme postarané
$zamestnanec = new Zamestnanec('Jožko Vajda'); // priamo vytvaráme objekt bez vkládania súborov
print($zamestnanec);
Výstup:
Jožko Vajda
Jak můžeme vidět, nevyvolalo nám to žádnou chybovou zprávu o tom, že tvoříme objekt z třídy, kterou jsme nikde vkládaly. Přece jsme ji vložili, ale nemuseli jsme to dělat my , udělal to za nás PHP skript. Využití autoloader-a je poměrně dost vysoké a setkáme se s ním téměř všude. Ruční vkládání souborů již ztratilo na své efektivitě a nahradila ho tato nádherná funkce.
NOVINKA! Na autoloader existuje už i funkce spl_autoload_register()
, která umožňuje využívat více než jen jeden autoloader. Rozdíl mezi __autoload()
a spl_autoload_register()
a jeho výhody naleznete na této adrese.
Toto je konec III. části našeho tutoriálu. IV. část se bude rovněž věnovat OOP ale bude zacházet trochu více do hloubky, kde si probereme přetěžování, návrhové vzory jako např. Singleton, reflexe apod.
Použitá literatura: Mistrovství v PHP 5, ISBN: 978-80-251-1519-0