Nette Documentation Preview

syntax
Úvod do objektově orientovaného programování
********************************************

.[perex]
Termín "OOP" označuje objektově orientované programování, což je způsob, jak organizovat a strukturovat kód. OOP nám umožňuje vidět program jako soubor objektů, které komunikují mezi sebou, místo sledu příkazů a funkcí.

V OOP je "objekt" jednotka, která obsahuje data a funkce, které s těmito daty pracují. Objekty jsou vytvořeny podle "tříd", které můžeme chápat jako návrhy nebo šablony pro objekty. Když máme třídu, můžeme vytvořit její "instanci", což je konkrétní objekt vytvořený podle této třídy.

Pojďme si ukázat, jak můžeme vytvořit jednoduchou třídu v PHP. Při definování třídy použijeme klíčové slovo "class", následované názvem třídy a pak složenými závorkami, které obklopují funkce (říká se jim "metody") a proměnné třídy (říká se jim "vlastnosti" nebo anglicky "property"):

```php
class Auto
{
	function zatrub()
	{
		echo 'Bip bip!';
	}
}
```

V tomto příkladě jsme vytvořili třídu s názvem `Auto` s jednou funkcí (nebo "metodou") nazvanou `zatrub`.

Každá třída by měla řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy.

Třídy obvykle ukládáme do samostatných souborů, aby byl kód organizovaný a snadno se v něm orientovalo. Název souboru by měl odpovídat názvu třídy, takže pro třídu `Auto` by název souboru byl `Auto.php`.

Při pojmenování tříd je dobré držet se konvence "PascalCase", což znamená, že každé slovo v názvu začíná velkým písmenem a nejsou mezi nimi žádné podtržítka nebo jiné oddělovače. Metody a vlastnosti používají konvenci "camelCase", to znamená, že začínají malým písmenem.

Některé metody v PHP mají speciální úlohy a jsou označené předponou `__` (dvě podtržítka). Jednou z nejdůležitějších speciálních metod je "konstruktor", který je označen jako `__construct`. Konstruktor je metoda, která se automaticky zavolá, když vytváříte novou instanci třídy.

Konstruktor často používáme k nastavení počátečního stavu objektu. Například, když vytváříte objekt reprezentující osobu, můžete využit konstruktor k nastavení jejího věku, jména nebo jiných vlastností.

Pojďme si ukázat, jak použít konstruktor v PHP:

```php
class Osoba
{
	private $vek;

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

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

$osoba = new Osoba(25);
echo $osoba->kolikJeTiLet(); // Vypíše: 25
```

V tomto příkladě třída `Osoba` má vlastnost `$vek` a dále konstruktor, který nastavuje tuto vlastnost. Metoda `kolikJeTiLet()` pak umožňuje přístup k věku osoby.

Klíčové slovo `new` se používá k vytvoření nové instance třídy. Ve výše uvedeném příkladu jsme vytvořili novou osobu s věkem 25.

Můžete také nastavit výchozí hodnoty pro parametry konstruktoru, pokud nejsou při vytváření objektu specifikovány. Například:

```php
class Osoba
{
	private $vek;

	function __construct($vek = 20)
	{
		$this->vek = $vek;
	}

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

$osoba = new Osoba;  // pokud nepředáváme žádný argument, lze závorky vynechat
echo $osoba->kolikJeTiLet(); // Vypíše: 20
```

V tomto příkladě, pokud nezadáte věk při vytváření objektu `Osoba`, bude použita výchozí hodnota 20.

A nakonec, definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit:

```php
class Osoba
{
	function __construct(
		private $vek = 20,
	) {
	}
}
```


Jmenné prostory
---------------

Jmenné prostory (neboli "namespaces" v angličtině) nám umožňují organizovat a seskupovat související třídy, funkce a konstanty, a zároveň se vyhýbat konfliktům v názvech. Můžete si je představit jako složky v počítači, kde každá složka obsahuje soubory, které patří k určitému projektu nebo tématu.

Jmenné prostory jsou obzvlášť užitečné ve větších projektech nebo když používáte knihovny od třetích stran, kde by mohly vzniknout konflikty v názvech tříd.

Představte si, že máte třídu s názvem `Auto` ve vašem projektu a chcete ji umístit do jmenného prostoru nazvaného `Doprava`. Uděláte to takto:

```php
namespace Doprava;

class Auto
{
	function zatrub()
	{
		echo 'Bip bip!';
	}
}
```

Pokud chcete použít třídu `Auto` v jiném souboru, musíte specifikovat, z jakého jmenného prostoru třída pochází:

```php
$auto = new Doprava\Auto;
```

Pro zjednodušení můžete na začátku souboru uvést, kterou třídu z daného jmenného prostoru chcete používat, což umožňuje vytvářet instance bez nutnosti uvádět celou cestu:

```php
use Doprava\Auto;

$auto = new Auto;
```


Dědičnost
---------

Dědičnost je nástrojem objektově orientovaného programování, který umožňuje vytvářet nové třídy na základě již existujících tříd, přebírat jejich vlastnosti a metody a rozšiřovat nebo předefinovat je podle potřeby. Dědičnost umožňuje zajistit kódovou znovupoužitelnost a hierarchii tříd.

Zjednodušeně řečeno, pokud máme jednu třídu a chtěli bychom vytvořit další, od ní odvozenou, ale s několika změnami, můžeme novou třídu "zdědit" z původní třídy.

V PHP dědičnost realizujeme pomocí klíčového slova `extends`.

Naše třída `Osoba` uchovává informaci o věku. Můžeme mít další třídu `Student`, která rozšiřuje `Osobu` a přidává informaci o oboru studia.

Podívejme se na příklad:

```php
class Osoba
{
	private $vek;

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

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

class Student extends Osoba
{
	private $obor;

	function __construct($vek, $obor)
	{
		parent::__construct($vek);
		$this->obor = $obor;
	}

	function vypisInformace()
	{
		echo 'Věk studenta: ', $this->kolikJeTiLet();
		echo 'Obor studia: ', $this->obor;
	}
}

$student = new Student(20, 'Informatika');
$student->vypisInformace();
```

Jak tento kód funguje?

- Použili jsme klíčové slovo `extends` k rozšíření třídy `Osoba`, což znamená, že třída `Student` zdědí všechny metody a vlastnosti z `Osoby`.

- Klíčové slovo `parent::` nám umožňuje volat metody z nadřazené třídy. V tomto případě jsme volali konstruktor z třídy `Osoba` před přidáním vlastní funkcionality do třídy `Student`.

Dědičnost je určená pro situace, kdy existuje vztah "je" mezi třídami. Například `Student` je `Osoba`. Kočka je zvíře. Dává nám možnost v případech, kdy v kódu očekáváme jeden objekt (např. "Osoba"), použít místo něj objekt zděděný (např. "Student").

Je důležité si uvědomit, že hlavním účelem dědičnosti **není** zabránit duplikaci kódu. Naopak, nesprávné využití dědičnosti může vést k složitému a těžko udržitelnému kódu. Pokud vztah "je" mezi třídami neexistuje, měli bychom místo dědičnosti uvažovat o kompozici.


Kompozice
---------

Kompozice je technika, kdy místo toho, abychom zdědili vlastnosti a metody jiné třídy, jednoduše využijeme její instanci v naší třídě. Toto nám umožňuje kombinovat funkcionality a vlastnosti více tříd bez nutnosti vytvářet složité dědičné struktury.

Podívejme se na příklad. Máme třídu `Motor` a třídu `Auto`. Místo toho, abychom říkali "Auto je Motor", říkáme "Auto má Motor", což je typický vztah kompozice.

```php
class Motor
{
	function zapni()
	{
		echo "Motor běží.";
	}
}

class Auto
{
	private $motor;

	function __construct()
	{
		$this->motor = new Motor;
	}

	function start()
	{
		$this->motor->zapni();
		echo "Auto je připraveno k jízdě!";
	}
}

$auto = new Auto;
$auto->start();
```

Zde `Auto` nemá všechny vlastnosti a metody `Motoru`, ale má k němu přístup prostřednictvím vlastnosti `$motor`.

Výhodou kompozice je větší flexibilita v designu a lepší možnost úprav v budoucnosti.


Viditelnost
-----------

V PHP můžete definovat "viditelnost" pro vlastnosti, metody a konstanty třídy. Viditelnost určuje, odkud můžete přistupovat k těmto prvkům.

1. **Public:** Pokud je prvek označen jako `public`, znamená to, že k němu můžete přistupovat odkudkoli, i mimo třídu.

2. **Protected:** Prvek s označením `protected` je přístupný pouze v rámci dané třídy a všech jejích potomků (tříd, které dědí od této třídy).

3. **Private:** Pokud je prvek `private`, můžete k němu přistupovat pouze zevnitř třídy, ve které byl definována.

Pokud nespecifikujete viditelnost, PHP ji automaticky nastaví na `public`.

Podívejme se na ukázkový kód:

```php
class UkazkaViditelnosti
{
	public $verejnaVlastnost = 'Veřejná';
	protected $chranenaVlastnost = 'Chráněná';
	private $soukromaVlastnost = 'Soukromá';

	public function vypisVlastnosti()
	{
		echo $this->verejnaVlastnost;  // Funguje
		echo $this->chranenaVlastnost; // Funguje
		echo $this->soukromaVlastnost; // Funguje
	}
}

$objekt = new UkazkaViditelnosti;
$objekt->vypisVlastnosti();
echo $objekt->verejnaVlastnost;      // Funguje
// echo $objekt->chranenaVlastnost;  // Vyhodí chybu
// echo $objekt->soukromaVlastnost;  // Vyhodí chybu
```

Pokračujeme s děděním třídy:

```php
class PotomekTridy extends UkazkaViditelnosti
{
	public function vypisVlastnosti()
	{
		echo $this->verejnaVlastnost;   // Funguje
		echo $this->chranenaVlastnost;  // Funguje
		// echo $this->soukromaVlastnost;  // Vyhodí chybu
	}
}
```

V tomto případě metoda `vypisVlastnosti()` v třídě `PotomekTřídy` může přistupovat k veřejným a chráněným vlastnostem, ale nemůže přistupovat k privátním vlastnostem rodičovské třídy.

Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu.


Klíčové slovo `final`
---------------------

V PHP můžeme použít klíčové slovo `final`, pokud chceme zabránit třídě, metodě nebo konstantě být zděděna nebo přepsána. Když označíme třídu jako `final`, nemůže být rozšířena. Když označíme metodu jako `final`, nemůže být v potomkovské třídě přepsána.

Vědomí, že určitá třída nebo metoda nebude dále upravována, nám umožňuje snáze provádět úpravy, aniž bychom se museli obávat možných konfliktů. Například můžeme přidat novou metodu bez obav, že by některý její potomek už stejně pojmenovanou metodu měl a došlo by ke kolizi. Nebo metodě můžeme pozměnit jejich parametry, neboť opět nehrozí, že způsobíme nesoulad s přepsanou metodou v potomkovi.

```php
final class FinalniTrida
{
}

// Následující kód vyvolá chybu, protože nemůžeme zdědit od finalní třídy.
class PotomekFinalniTridy extends FinalniTrida
{
}
```

V tomto příkladu pokus o zdědění od finalní třídy `FinalniTrida` vyvolá chybu.


Statické vlastnosti a metody
----------------------------

Když v PHP mluvíme o "statických" prvcích třídy, myslíme tím metody a vlastnosti, které náleží samotné třídě, a ne konkrétní instanci této třídy. To znamená, že nemusíte vytvářet instanci třídy, abyste k nim měli přístup. Místo toho je voláte nebo přistupujete k nim přímo přes název třídy.

Mějte na paměti, že jelikož statické prvky patří k třídě, a ne k jejím instancím, nemůžete uvnitř statických metod používat pseudoproměnnou `$this`.

Používání statických vlastností vede k [nepřehlednému kódu plnému záludností|dependency-injection:global-state], proto byste je neměli nikdy použít a ani tu nebudeme ukazovat příklad použití. Naproti tomu statické metody jsou užitečné. Příklad použití:

```php
class Kalkulator
{
	public static function scitani($a, $b)
	{
		return $a + $b;
	}

	public static function odecitani($a, $b)
	{
		return $a - $b;
	}
}

// Použití statické metody bez vytvoření instance třídy
echo Kalkulator::scitani(5, 3); // Výsledek: 8
echo Kalkulator::odecitani(5, 3); // Výsledek: 2
```

V tomto příkladu jsme vytvořili třídu `Kalkulator` s dvěma statickými metodami. Tyto metody můžeme volat přímo bez vytvoření instance třídy pomocí `::` operátoru. Statické metody jsou obzvláště užitečné pro operace, které nezávisí na stavu konkrétní instance třídy.


Třídní konstanty
----------------

V rámci tříd máme možnost definovat konstanty. Konstanty jsou hodnoty, které se nikdy nezmění během běhu programu. Na rozdíl od proměnných, hodnota konstanty zůstává stále stejná.

```php
class Auto
{
	public const PocetKol = 4;

	public function zobrazPocetKol(): int
	{
		echo self::PocetKol;
	}
}

echo Auto::PocetKol;  // Výstup: 4
```

V tomto příkladu máme třídu `Auto` s konstantou `PocetKol`. Když chceme přistupovat ke konstantě uvnitř třídy, můžeme použít klíčové slovo `self` místo názvu třídy.


Objektová rozhraní
------------------

Objektová rozhraní fungují jako "smlouvy" pro třídy. Pokud má třída implementovat objektové rozhraní, musí obsahovat všechny metody, které toto rozhraní definuje. Je to skvělý způsob, jak zajistit, že určité třídy dodržují stejnou "smlouvu" nebo strukturu.

V PHP se rozhraní definuje klíčovým slovem `interface`. Všechny metody definované v rozhraní jsou veřejné (`public`). Když třída implementuje rozhraní, používá klíčové slovo `implements`.

```php
interface Zvire
{
	function vydejZvuk();
}

class Kocka implements Zvire
{
	public function vydejZvuk()
	{
		echo 'Mňau';
	}
}

$kocka = new Kocka;
$kocka->vydejZvuk();
```

Pokud třída implementuje rozhraní, ale nejsou v ní definované všechny očekávané metody, PHP vyhodí chybu. Třída může implementovat více rozhraní najednou, což je rozdíl oproti dědičnosti, kde může třída dědit pouze od jedné třídy.


Abstraktní třídy
----------------

Abstraktní třídy slouží jako základní šablony pro jiné třídy, ale nemůžete vytvářet jejich instance přímo. Obsahují kombinaci kompletních metod a abstraktních metod, které nemají definovaný obsah. Třídy, které dědí z abstraktních tříd, musí poskytnout definice pro všechny abstraktní metody z předka.

K definování abstraktní třídy používáme klíčové slovo `abstract`.

```php
abstract class AbstraktniTrida
{
	public function obycejnaMetoda()
	{
		echo 'Toto je obyčejná metoda';
	}

	abstract public function abstraktniMetoda();
}

class Potomek extends AbstraktniTrida
{
	public function abstraktniMetoda()
	{
		echo 'Toto je implementace abstraktní metody';
	}
}

$instance = new Potomek;
$instance->obycejnaMetoda();
$instance->abstraktniMetoda();
```

V tomto příkladu máme abstraktní třídu s jednou obyčejnou a jednou abstraktní metodou. Poté máme třídu `Potomek`, která dědí z `AbstraktniTrida` a poskytuje implementaci pro abstraktní metodu.


Typová kontrola
---------------

V programování je velmi důležité mít jistotu, že data, se kterými pracujeme, jsou správného typu. V PHP máme nástroje, které nám toto zajišťují. Ověřování, zda data mají správný typ, se nazývá "typová kontrola".

Typy, na které můžeme v PHP narazit:

1. **Základní typy**: Zahrnují `int` (celá čísla), `float` (desetinná čísla), `bool` (pravdivostní hodnoty), `string` (řetězce), `array` (pole) a `null`.
2. **Třídy**: Pokud chceme, aby hodnota byla instancí specifické třídy.
3. **Rozhraní**: Definuje soubor metod, které třída musí implementovat. Hodnota, která splňuje rozhraní, musí mít tyto metody.
4. **Smíšené typy**: Můžeme určit, že proměnná může mít více povolených typů.
5. **Void**: Tento speciální typ označuje, že funkce či metoda nevrací žádnou hodnotu.

Pojďme si ukázat, jak upravit kód, aby zahrnoval typy:

```php
class Osoba
{
	private int $vek;

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

	public function vypisVek(): void
	{
		echo "Této osobě je " . $this->vek . " let.";
	}
}

/**
 * Funkce, která přijímá objekt třídy Osoba a vypíše věk osoby.
 */
function vypisVekOsoby(Osoba $osoba): void
{
	$osoba->vypisVek();
}
```

Tímto způsobem jsme zajistili, že náš kód očekává a pracuje s daty správného typu, což nám pomáhá předcházet potenciálním chybám.


Porovnávání a identita
----------------------

V PHP můžete porovnávat objekty dvěma způsoby:

1. Porovnání hodnot `==`: Zkontroluje, zda mají objekty jsou stejné třídy a mají stejné hodnoty ve svých vlastnostech.
2. Identita `===`: Zkontroluje, zda jde o stejnou instanci objektu.

```php
class Auto
{
	public string $znacka;

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

$auto1 = new Auto('Skoda');
$auto2 = new Auto('Skoda');
$auto3 = $auto1;

var_dump($auto1 == $auto2);   // true, protože mají stejnou hodnotu
var_dump($auto1 === $auto2);  // false, protože nejsou stejná instance
var_dump($auto1 === $auto3);  // true, protože $auto3 je stejná instance jako $auto1
```


Operátor `instanceof`
---------------------

Operátor `instanceof` umožňuje zjistit, zda je daný objekt instancí určité třídy, potomka této třídy, nebo zda implementuje určité rozhraní.

Představme si, že máme třídu `Osoba` a další třídu `Student`, která je potomkem třídy `Osoba`:

```php
class Osoba
{
	private int $vek;

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

class Student extends Osoba
{
	private string $obor;

	public function __construct(int $vek, string $obor)
	{
		parent::__construct($vek);
		$this->obor = $obor;
	}
}

$student = new Student(20, 'Informatika');

// Ověření, zda je $student instancí třídy Student
var_dump($student instanceof Student);  // Výstup: bool(true)

// Ověření, zda je $student instancí třídy Osoba (protože Student je potomek Osoba)
var_dump($student instanceof Osoba);     // Výstup: bool(true)
```

Z výstupů je patrné, že objekt `$student` je současně považován za instanci obou tříd - `Student` i `Osoba`.


Fluent Interfaces
-----------------

"Plynulé rozhraní" (anglicky "Fluent Interface") je technika v OOP, která umožňuje řetězit metody dohromady v jednom volání. Tím se často zjednoduší a zpřehlední kód.

Klíčovým prvkem plynulého rozhraní je, že každá metoda v řetězu vrací odkaz na aktuální objekt. Toho dosáhneme tak, že na konci metody použijeme `return $this;`. Tento styl programování je často spojován s metodami zvanými "setters", které nastavují hodnoty vlastností objektu.

Ukážeme si, jak může vypadat plynulé rozhraní na příkladu odesílání emailů:

```php
public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}
```

V tomto příkladě metody `setFrom()`, `setRecipient()` a `setMessage()` slouží k nastavení odpovídajících hodnot (odesílatele, příjemce, obsahu zprávy). Po nastavení každé z těchto hodnot nám metody vrací aktuální objekt (`$email`), což nám umožňuje řetězit další metodu za ní. Nakonec voláme metodu `send()`, která email skutečně odesílá.

Díky plynulým rozhraním můžeme psát kód, který je intuitivní a snadno čitelný.


Kopírování pomocí `clone`
-------------------------

V PHP můžeme vytvořit kopii objektu pomocí operátoru `clone`. Tímto způsobem dostaneme novou instanci s totožným obsahem.

Pokud potřebujeme při kopírování objektu upravit některé jeho vlastnosti, můžeme ve třídě definovat speciální metodu `__clone()`. Tato metoda se automaticky zavolá, když je objekt klonován.

```php
class Ovce
{
	public string $jmeno;

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

	public function __clone()
	{
		$this->jmeno = 'Klon ' . $this->jmeno;
	}
}

$original = new Ovce('Dolly');
echo $original->jmeno . "\n";  // Vypíše: Dolly

$klon = clone $original;
echo $klon->jmeno . "\n";      // Vypíše: Klon Dolly
```

V tomto příkladu máme třídu `Ovce` s jednou vlastností `$jmeno`. Když klonujeme instanci této třídy, metoda `__clone()` se postará o to, aby název klonované ovce získal předponu "Klon".


Traity
------

Traity v PHP jsou nástrojem, který umožňuje sdílet metody, vlastnosti a konstanty mezi třídami a zabránit duplicitě kódu. Můžete si je představit jako mechanismus "kopírovat a vložit" (Ctrl-C a Ctrl-V), kdy se obsah trait "vkládá" do tříd. To vám umožní znovupoužívat kód bez nutnosti vytvářet komplikované hierarchie tříd.

Pojďme si ukázat jednoduchý příklad, jak používat traity v PHP:

```php
trait Troubeni
{
	public function zatrub()
	{
		echo 'Bip bip!';
	}
}

class Auto
{
	use Troubeni;
}

class Nakladak
{
	use Troubeni;
}

$auto = new Auto;
$auto->zatrub(); // Vypíše 'Bip bip!'

$nakladak = new Nakladak;
$nakladak->zatrub(); // Také vypíše 'Bip bip!'
```

V tomto příkladu máme traitu nazvanou `Troubeni`, která obsahuje jednu metodu `zatrub()`. Poté máme dvě třídy: `Auto` a `Nakladak`, které obě používají traitu `Troubeni`. Díky tomu obě třídy "mají" metodu `zatrub()`, a můžeme ji volat na objektech obou tříd.

Traity vám umožní snadno a efektivně sdílet kód mezi třídami. Přitom nevstupují do dědičné hierarchie, tj. `$auto instanceof Troubeni` vrátí `false`.


Výjimky
-------

Výjimky v OOP nám umožňují řídit a ošetřovat chyby, které mohou vzniknout během provádění našeho kódu. Jsou to vlastně objekty určené k zaznamenání chyb nebo nečekaných situací ve vašem programu.

V PHP máme pro tyto objekty k dispozici vestavěnou třídu `Exception`. Ta má několik metod, které nám umožňují získat více informací o výjimce, jako je zpráva o chybě, soubor a řádek, kde k chybě došlo, atd.

Když se objeví problém, můžeme "vyhodit" výjimku (použitím `throw`). Chceme-li tuto výjimku "chytit" a nějak zpracovat, použijeme bloky `try` a `catch`.

Ukážeme si, jak to funguje:

```php
try {
	throw new Exception('Zpráva vysvětlující důvod výjimky');

	// Tento kód se neprovede
	echo 'Jsem zpráva, kterou nikdo nepřečte';

} catch (Exception $e) {
	echo 'Výjimka zachycena: '. $e->getMessage();
}
```

Důležité je, že výjimka může být vyhozena hlouběji, při volání jiných metod.

Pro jeden blok `try` lze uvést více bloků `catch`, pokud očekáváte různé typy výjimek.

Zároveň můžeme vytvořit hierarchii výjimek, kde každá třída výjimky dědí od té předchozí. Jako příklad si představme jednoduchou bankovní aplikaci, která umožňuje provádět vklady a výběry:

```php
class BankovniVyjimka extends Exception {}
class NedostatekProstredkuVyjimka extends BankovniVyjimka {}
class PrekroceniLimituVyjimka extends BankovniVyjimka {}

class BankovniUcet
{
	private int $zustatek = 0;
	private int $denniLimit = 1000;

	public function vlozit(int $castka): int
	{
		$this->zustatek += $castka;
		return $this->zustatek;
	}

	public function vybrat(int $castka): int
	{
		if ($castka > $this->zustatek) {
			throw new NedostatekProstredkuVyjimka('Na účtu není dostatek prostředků.');
		}

		if ($castka > $this->denniLimit) {
			throw new PrekroceniLimituVyjimka('Byl překročen denní limit pro výběry.');
		}

		$this->zustatek -= $castka;
		return $this->zustatek;
	}
}

$ucet = new BankovniUcet;
$ucet->vlozit(500);

try {
	$ucet->vybrat(1500);
} catch (PrekroceniLimituVyjimka $e) {
	echo $e->getMessage();
} catch (NedostatekProstredkuVyjimka $e) {
	echo $e->getMessage();
} catch (BankovniVyjimka $e) {
	echo 'Vyskytla se chyba při provádění operace.';
}
```

V tomto příkladu je důležité si všimnout pořadí bloků `catch`. Protože všechny výjimky dědí od `BankovniVyjimka`, pokud bychom tento blok měli první, zachytily by se v něm všechny výjimky, aniž by se kód dostal k následujícím `catch` blokům. Proto je důležité mít specifičtější výjimky (tj. ty, které dědí od jiných) v bloku `catch` výše v pořadí než jejich rodičovské výjimky.


Správné postupy
---------------

Když máte za sebou základní principy objektově orientovaného programování, je důležité se zaměřit na správné postupy v OOP. Ty vám pomohou psat kód, který je nejen funkční, ale také čitelný, srozumitelný a snadno udržovatelný.

1) **Oddělení zájmů (Separation of Concerns)**: Každá třída by měla mít jasně definovanou odpovědnost a měla by řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy.
2) **Zapouzdření (Encapsulation)**: Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu.
3) **Předávání závislostí (Dependency Injection)**: Místo toho, abyste vytvořili závislosti přímo v třídě, měli byste je "injektovat" z vnějšku. Pro hlubší porozumění tomuto principu doporučujeme [kapitoly o Dependency Injection|dependency-injection:introduction].

Úvod do objektově orientovaného programování

Termín „OOP“ označuje objektově orientované programování, což je způsob, jak organizovat a strukturovat kód. OOP nám umožňuje vidět program jako soubor objektů, které komunikují mezi sebou, místo sledu příkazů a funkcí.

V OOP je „objekt“ jednotka, která obsahuje data a funkce, které s těmito daty pracují. Objekty jsou vytvořeny podle „tříd“, které můžeme chápat jako návrhy nebo šablony pro objekty. Když máme třídu, můžeme vytvořit její „instanci“, což je konkrétní objekt vytvořený podle této třídy.

Pojďme si ukázat, jak můžeme vytvořit jednoduchou třídu v PHP. Při definování třídy použijeme klíčové slovo „class“, následované názvem třídy a pak složenými závorkami, které obklopují funkce (říká se jim „metody“) a proměnné třídy (říká se jim „vlastnosti“ nebo anglicky „property“):

class Auto
{
	function zatrub()
	{
		echo 'Bip bip!';
	}
}

V tomto příkladě jsme vytvořili třídu s názvem Auto s jednou funkcí (nebo „metodou“) nazvanou zatrub.

Každá třída by měla řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy.

Třídy obvykle ukládáme do samostatných souborů, aby byl kód organizovaný a snadno se v něm orientovalo. Název souboru by měl odpovídat názvu třídy, takže pro třídu Auto by název souboru byl Auto.php.

Při pojmenování tříd je dobré držet se konvence „PascalCase“, což znamená, že každé slovo v názvu začíná velkým písmenem a nejsou mezi nimi žádné podtržítka nebo jiné oddělovače. Metody a vlastnosti používají konvenci „camelCase“, to znamená, že začínají malým písmenem.

Některé metody v PHP mají speciální úlohy a jsou označené předponou __ (dvě podtržítka). Jednou z nejdůležitějších speciálních metod je „konstruktor“, který je označen jako __construct. Konstruktor je metoda, která se automaticky zavolá, když vytváříte novou instanci třídy.

Konstruktor často používáme k nastavení počátečního stavu objektu. Například, když vytváříte objekt reprezentující osobu, můžete využit konstruktor k nastavení jejího věku, jména nebo jiných vlastností.

Pojďme si ukázat, jak použít konstruktor v PHP:

class Osoba
{
	private $vek;

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

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

$osoba = new Osoba(25);
echo $osoba->kolikJeTiLet(); // Vypíše: 25

V tomto příkladě třída Osoba má vlastnost $vek a dále konstruktor, který nastavuje tuto vlastnost. Metoda kolikJeTiLet() pak umožňuje přístup k věku osoby.

Klíčové slovo new se používá k vytvoření nové instance třídy. Ve výše uvedeném příkladu jsme vytvořili novou osobu s věkem 25.

Můžete také nastavit výchozí hodnoty pro parametry konstruktoru, pokud nejsou při vytváření objektu specifikovány. Například:

class Osoba
{
	private $vek;

	function __construct($vek = 20)
	{
		$this->vek = $vek;
	}

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

$osoba = new Osoba;  // pokud nepředáváme žádný argument, lze závorky vynechat
echo $osoba->kolikJeTiLet(); // Vypíše: 20

V tomto příkladě, pokud nezadáte věk při vytváření objektu Osoba, bude použita výchozí hodnota 20.

A nakonec, definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit:

class Osoba
{
	function __construct(
		private $vek = 20,
	) {
	}
}

Jmenné prostory

Jmenné prostory (neboli „namespaces“ v angličtině) nám umožňují organizovat a seskupovat související třídy, funkce a konstanty, a zároveň se vyhýbat konfliktům v názvech. Můžete si je představit jako složky v počítači, kde každá složka obsahuje soubory, které patří k určitému projektu nebo tématu.

Jmenné prostory jsou obzvlášť užitečné ve větších projektech nebo když používáte knihovny od třetích stran, kde by mohly vzniknout konflikty v názvech tříd.

Představte si, že máte třídu s názvem Auto ve vašem projektu a chcete ji umístit do jmenného prostoru nazvaného Doprava. Uděláte to takto:

namespace Doprava;

class Auto
{
	function zatrub()
	{
		echo 'Bip bip!';
	}
}

Pokud chcete použít třídu Auto v jiném souboru, musíte specifikovat, z jakého jmenného prostoru třída pochází:

$auto = new Doprava\Auto;

Pro zjednodušení můžete na začátku souboru uvést, kterou třídu z daného jmenného prostoru chcete používat, což umožňuje vytvářet instance bez nutnosti uvádět celou cestu:

use Doprava\Auto;

$auto = new Auto;

Dědičnost

Dědičnost je nástrojem objektově orientovaného programování, který umožňuje vytvářet nové třídy na základě již existujících tříd, přebírat jejich vlastnosti a metody a rozšiřovat nebo předefinovat je podle potřeby. Dědičnost umožňuje zajistit kódovou znovupoužitelnost a hierarchii tříd.

Zjednodušeně řečeno, pokud máme jednu třídu a chtěli bychom vytvořit další, od ní odvozenou, ale s několika změnami, můžeme novou třídu „zdědit“ z původní třídy.

V PHP dědičnost realizujeme pomocí klíčového slova extends.

Naše třída Osoba uchovává informaci o věku. Můžeme mít další třídu Student, která rozšiřuje Osobu a přidává informaci o oboru studia.

Podívejme se na příklad:

class Osoba
{
	private $vek;

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

	function kolikJeTiLet()
	{
		return $this->vek;
	}
}

class Student extends Osoba
{
	private $obor;

	function __construct($vek, $obor)
	{
		parent::__construct($vek);
		$this->obor = $obor;
	}

	function vypisInformace()
	{
		echo 'Věk studenta: ', $this->kolikJeTiLet();
		echo 'Obor studia: ', $this->obor;
	}
}

$student = new Student(20, 'Informatika');
$student->vypisInformace();

Jak tento kód funguje?

  • Použili jsme klíčové slovo extends k rozšíření třídy Osoba, což znamená, že třída Student zdědí všechny metody a vlastnosti z Osoby.
  • Klíčové slovo parent:: nám umožňuje volat metody z nadřazené třídy. V tomto případě jsme volali konstruktor z třídy Osoba před přidáním vlastní funkcionality do třídy Student.

Dědičnost je určená pro situace, kdy existuje vztah „je“ mezi třídami. Například Student je Osoba. Kočka je zvíře. Dává nám možnost v případech, kdy v kódu očekáváme jeden objekt (např. „Osoba“), použít místo něj objekt zděděný (např. „Student“).

Je důležité si uvědomit, že hlavním účelem dědičnosti není zabránit duplikaci kódu. Naopak, nesprávné využití dědičnosti může vést k složitému a těžko udržitelnému kódu. Pokud vztah „je“ mezi třídami neexistuje, měli bychom místo dědičnosti uvažovat o kompozici.

Kompozice

Kompozice je technika, kdy místo toho, abychom zdědili vlastnosti a metody jiné třídy, jednoduše využijeme její instanci v naší třídě. Toto nám umožňuje kombinovat funkcionality a vlastnosti více tříd bez nutnosti vytvářet složité dědičné struktury.

Podívejme se na příklad. Máme třídu Motor a třídu Auto. Místo toho, abychom říkali „Auto je Motor“, říkáme „Auto má Motor“, což je typický vztah kompozice.

class Motor
{
	function zapni()
	{
		echo "Motor běží.";
	}
}

class Auto
{
	private $motor;

	function __construct()
	{
		$this->motor = new Motor;
	}

	function start()
	{
		$this->motor->zapni();
		echo "Auto je připraveno k jízdě!";
	}
}

$auto = new Auto;
$auto->start();

Zde Auto nemá všechny vlastnosti a metody Motoru, ale má k němu přístup prostřednictvím vlastnosti $motor.

Výhodou kompozice je větší flexibilita v designu a lepší možnost úprav v budoucnosti.

Viditelnost

V PHP můžete definovat „viditelnost“ pro vlastnosti, metody a konstanty třídy. Viditelnost určuje, odkud můžete přistupovat k těmto prvkům.

  1. Public: Pokud je prvek označen jako public, znamená to, že k němu můžete přistupovat odkudkoli, i mimo třídu.
  2. Protected: Prvek s označením protected je přístupný pouze v rámci dané třídy a všech jejích potomků (tříd, které dědí od této třídy).
  3. Private: Pokud je prvek private, můžete k němu přistupovat pouze zevnitř třídy, ve které byl definována.

Pokud nespecifikujete viditelnost, PHP ji automaticky nastaví na public.

Podívejme se na ukázkový kód:

class UkazkaViditelnosti
{
	public $verejnaVlastnost = 'Veřejná';
	protected $chranenaVlastnost = 'Chráněná';
	private $soukromaVlastnost = 'Soukromá';

	public function vypisVlastnosti()
	{
		echo $this->verejnaVlastnost;  // Funguje
		echo $this->chranenaVlastnost; // Funguje
		echo $this->soukromaVlastnost; // Funguje
	}
}

$objekt = new UkazkaViditelnosti;
$objekt->vypisVlastnosti();
echo $objekt->verejnaVlastnost;      // Funguje
// echo $objekt->chranenaVlastnost;  // Vyhodí chybu
// echo $objekt->soukromaVlastnost;  // Vyhodí chybu

Pokračujeme s děděním třídy:

class PotomekTridy extends UkazkaViditelnosti
{
	public function vypisVlastnosti()
	{
		echo $this->verejnaVlastnost;   // Funguje
		echo $this->chranenaVlastnost;  // Funguje
		// echo $this->soukromaVlastnost;  // Vyhodí chybu
	}
}

V tomto případě metoda vypisVlastnosti() v třídě PotomekTřídy může přistupovat k veřejným a chráněným vlastnostem, ale nemůže přistupovat k privátním vlastnostem rodičovské třídy.

Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu.

Klíčové slovo final

V PHP můžeme použít klíčové slovo final, pokud chceme zabránit třídě, metodě nebo konstantě být zděděna nebo přepsána. Když označíme třídu jako final, nemůže být rozšířena. Když označíme metodu jako final, nemůže být v potomkovské třídě přepsána.

Vědomí, že určitá třída nebo metoda nebude dále upravována, nám umožňuje snáze provádět úpravy, aniž bychom se museli obávat možných konfliktů. Například můžeme přidat novou metodu bez obav, že by některý její potomek už stejně pojmenovanou metodu měl a došlo by ke kolizi. Nebo metodě můžeme pozměnit jejich parametry, neboť opět nehrozí, že způsobíme nesoulad s přepsanou metodou v potomkovi.

final class FinalniTrida
{
}

// Následující kód vyvolá chybu, protože nemůžeme zdědit od finalní třídy.
class PotomekFinalniTridy extends FinalniTrida
{
}

V tomto příkladu pokus o zdědění od finalní třídy FinalniTrida vyvolá chybu.

Statické vlastnosti a metody

Když v PHP mluvíme o „statických“ prvcích třídy, myslíme tím metody a vlastnosti, které náleží samotné třídě, a ne konkrétní instanci této třídy. To znamená, že nemusíte vytvářet instanci třídy, abyste k nim měli přístup. Místo toho je voláte nebo přistupujete k nim přímo přes název třídy.

Mějte na paměti, že jelikož statické prvky patří k třídě, a ne k jejím instancím, nemůžete uvnitř statických metod používat pseudoproměnnou $this.

Používání statických vlastností vede k nepřehlednému kódu plnému záludností, proto byste je neměli nikdy použít a ani tu nebudeme ukazovat příklad použití. Naproti tomu statické metody jsou užitečné. Příklad použití:

class Kalkulator
{
	public static function scitani($a, $b)
	{
		return $a + $b;
	}

	public static function odecitani($a, $b)
	{
		return $a - $b;
	}
}

// Použití statické metody bez vytvoření instance třídy
echo Kalkulator::scitani(5, 3); // Výsledek: 8
echo Kalkulator::odecitani(5, 3); // Výsledek: 2

V tomto příkladu jsme vytvořili třídu Kalkulator s dvěma statickými metodami. Tyto metody můžeme volat přímo bez vytvoření instance třídy pomocí :: operátoru. Statické metody jsou obzvláště užitečné pro operace, které nezávisí na stavu konkrétní instance třídy.

Třídní konstanty

V rámci tříd máme možnost definovat konstanty. Konstanty jsou hodnoty, které se nikdy nezmění během běhu programu. Na rozdíl od proměnných, hodnota konstanty zůstává stále stejná.

class Auto
{
	public const PocetKol = 4;

	public function zobrazPocetKol(): int
	{
		echo self::PocetKol;
	}
}

echo Auto::PocetKol;  // Výstup: 4

V tomto příkladu máme třídu Auto s konstantou PocetKol. Když chceme přistupovat ke konstantě uvnitř třídy, můžeme použít klíčové slovo self místo názvu třídy.

Objektová rozhraní

Objektová rozhraní fungují jako „smlouvy“ pro třídy. Pokud má třída implementovat objektové rozhraní, musí obsahovat všechny metody, které toto rozhraní definuje. Je to skvělý způsob, jak zajistit, že určité třídy dodržují stejnou „smlouvu“ nebo strukturu.

V PHP se rozhraní definuje klíčovým slovem interface. Všechny metody definované v rozhraní jsou veřejné (public). Když třída implementuje rozhraní, používá klíčové slovo implements.

interface Zvire
{
	function vydejZvuk();
}

class Kocka implements Zvire
{
	public function vydejZvuk()
	{
		echo 'Mňau';
	}
}

$kocka = new Kocka;
$kocka->vydejZvuk();

Pokud třída implementuje rozhraní, ale nejsou v ní definované všechny očekávané metody, PHP vyhodí chybu. Třída může implementovat více rozhraní najednou, což je rozdíl oproti dědičnosti, kde může třída dědit pouze od jedné třídy.

Abstraktní třídy

Abstraktní třídy slouží jako základní šablony pro jiné třídy, ale nemůžete vytvářet jejich instance přímo. Obsahují kombinaci kompletních metod a abstraktních metod, které nemají definovaný obsah. Třídy, které dědí z abstraktních tříd, musí poskytnout definice pro všechny abstraktní metody z předka.

K definování abstraktní třídy používáme klíčové slovo abstract.

abstract class AbstraktniTrida
{
	public function obycejnaMetoda()
	{
		echo 'Toto je obyčejná metoda';
	}

	abstract public function abstraktniMetoda();
}

class Potomek extends AbstraktniTrida
{
	public function abstraktniMetoda()
	{
		echo 'Toto je implementace abstraktní metody';
	}
}

$instance = new Potomek;
$instance->obycejnaMetoda();
$instance->abstraktniMetoda();

V tomto příkladu máme abstraktní třídu s jednou obyčejnou a jednou abstraktní metodou. Poté máme třídu Potomek, která dědí z AbstraktniTrida a poskytuje implementaci pro abstraktní metodu.

Typová kontrola

V programování je velmi důležité mít jistotu, že data, se kterými pracujeme, jsou správného typu. V PHP máme nástroje, které nám toto zajišťují. Ověřování, zda data mají správný typ, se nazývá „typová kontrola“.

Typy, na které můžeme v PHP narazit:

  1. Základní typy: Zahrnují int (celá čísla), float (desetinná čísla), bool (pravdivostní hodnoty), string (řetězce), array (pole) a null.
  2. Třídy: Pokud chceme, aby hodnota byla instancí specifické třídy.
  3. Rozhraní: Definuje soubor metod, které třída musí implementovat. Hodnota, která splňuje rozhraní, musí mít tyto metody.
  4. Smíšené typy: Můžeme určit, že proměnná může mít více povolených typů.
  5. Void: Tento speciální typ označuje, že funkce či metoda nevrací žádnou hodnotu.

Pojďme si ukázat, jak upravit kód, aby zahrnoval typy:

class Osoba
{
	private int $vek;

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

	public function vypisVek(): void
	{
		echo "Této osobě je " . $this->vek . " let.";
	}
}

/**
 * Funkce, která přijímá objekt třídy Osoba a vypíše věk osoby.
 */
function vypisVekOsoby(Osoba $osoba): void
{
	$osoba->vypisVek();
}

Tímto způsobem jsme zajistili, že náš kód očekává a pracuje s daty správného typu, což nám pomáhá předcházet potenciálním chybám.

Porovnávání a identita

V PHP můžete porovnávat objekty dvěma způsoby:

  1. Porovnání hodnot ==: Zkontroluje, zda mají objekty jsou stejné třídy a mají stejné hodnoty ve svých vlastnostech.
  2. Identita ===: Zkontroluje, zda jde o stejnou instanci objektu.
class Auto
{
	public string $znacka;

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

$auto1 = new Auto('Skoda');
$auto2 = new Auto('Skoda');
$auto3 = $auto1;

var_dump($auto1 == $auto2);   // true, protože mají stejnou hodnotu
var_dump($auto1 === $auto2);  // false, protože nejsou stejná instance
var_dump($auto1 === $auto3);  // true, protože $auto3 je stejná instance jako $auto1

Operátor instanceof

Operátor instanceof umožňuje zjistit, zda je daný objekt instancí určité třídy, potomka této třídy, nebo zda implementuje určité rozhraní.

Představme si, že máme třídu Osoba a další třídu Student, která je potomkem třídy Osoba:

class Osoba
{
	private int $vek;

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

class Student extends Osoba
{
	private string $obor;

	public function __construct(int $vek, string $obor)
	{
		parent::__construct($vek);
		$this->obor = $obor;
	}
}

$student = new Student(20, 'Informatika');

// Ověření, zda je $student instancí třídy Student
var_dump($student instanceof Student);  // Výstup: bool(true)

// Ověření, zda je $student instancí třídy Osoba (protože Student je potomek Osoba)
var_dump($student instanceof Osoba);     // Výstup: bool(true)

Z výstupů je patrné, že objekt $student je současně považován za instanci obou tříd – Student i Osoba.

Fluent Interfaces

„Plynulé rozhraní“ (anglicky „Fluent Interface“) je technika v OOP, která umožňuje řetězit metody dohromady v jednom volání. Tím se často zjednoduší a zpřehlední kód.

Klíčovým prvkem plynulého rozhraní je, že každá metoda v řetězu vrací odkaz na aktuální objekt. Toho dosáhneme tak, že na konci metody použijeme return $this;. Tento styl programování je často spojován s metodami zvanými „setters“, které nastavují hodnoty vlastností objektu.

Ukážeme si, jak může vypadat plynulé rozhraní na příkladu odesílání emailů:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

V tomto příkladě metody setFrom(), setRecipient() a setMessage() slouží k nastavení odpovídajících hodnot (odesílatele, příjemce, obsahu zprávy). Po nastavení každé z těchto hodnot nám metody vrací aktuální objekt ($email), což nám umožňuje řetězit další metodu za ní. Nakonec voláme metodu send(), která email skutečně odesílá.

Díky plynulým rozhraním můžeme psát kód, který je intuitivní a snadno čitelný.

Kopírování pomocí clone

V PHP můžeme vytvořit kopii objektu pomocí operátoru clone. Tímto způsobem dostaneme novou instanci s totožným obsahem.

Pokud potřebujeme při kopírování objektu upravit některé jeho vlastnosti, můžeme ve třídě definovat speciální metodu __clone(). Tato metoda se automaticky zavolá, když je objekt klonován.

class Ovce
{
	public string $jmeno;

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

	public function __clone()
	{
		$this->jmeno = 'Klon ' . $this->jmeno;
	}
}

$original = new Ovce('Dolly');
echo $original->jmeno . "\n";  // Vypíše: Dolly

$klon = clone $original;
echo $klon->jmeno . "\n";      // Vypíše: Klon Dolly

V tomto příkladu máme třídu Ovce s jednou vlastností $jmeno. Když klonujeme instanci této třídy, metoda __clone() se postará o to, aby název klonované ovce získal předponu „Klon“.

Traity

Traity v PHP jsou nástrojem, který umožňuje sdílet metody, vlastnosti a konstanty mezi třídami a zabránit duplicitě kódu. Můžete si je představit jako mechanismus „kopírovat a vložit“ (Ctrl-C a Ctrl-V), kdy se obsah trait „vkládá“ do tříd. To vám umožní znovupoužívat kód bez nutnosti vytvářet komplikované hierarchie tříd.

Pojďme si ukázat jednoduchý příklad, jak používat traity v PHP:

trait Troubeni
{
	public function zatrub()
	{
		echo 'Bip bip!';
	}
}

class Auto
{
	use Troubeni;
}

class Nakladak
{
	use Troubeni;
}

$auto = new Auto;
$auto->zatrub(); // Vypíše 'Bip bip!'

$nakladak = new Nakladak;
$nakladak->zatrub(); // Také vypíše 'Bip bip!'

V tomto příkladu máme traitu nazvanou Troubeni, která obsahuje jednu metodu zatrub(). Poté máme dvě třídy: Auto a Nakladak, které obě používají traitu Troubeni. Díky tomu obě třídy „mají“ metodu zatrub(), a můžeme ji volat na objektech obou tříd.

Traity vám umožní snadno a efektivně sdílet kód mezi třídami. Přitom nevstupují do dědičné hierarchie, tj. $auto instanceof Troubeni vrátí false.

Výjimky

Výjimky v OOP nám umožňují řídit a ošetřovat chyby, které mohou vzniknout během provádění našeho kódu. Jsou to vlastně objekty určené k zaznamenání chyb nebo nečekaných situací ve vašem programu.

V PHP máme pro tyto objekty k dispozici vestavěnou třídu Exception. Ta má několik metod, které nám umožňují získat více informací o výjimce, jako je zpráva o chybě, soubor a řádek, kde k chybě došlo, atd.

Když se objeví problém, můžeme „vyhodit“ výjimku (použitím throw). Chceme-li tuto výjimku „chytit“ a nějak zpracovat, použijeme bloky try a catch.

Ukážeme si, jak to funguje:

try {
	throw new Exception('Zpráva vysvětlující důvod výjimky');

	// Tento kód se neprovede
	echo 'Jsem zpráva, kterou nikdo nepřečte';

} catch (Exception $e) {
	echo 'Výjimka zachycena: '. $e->getMessage();
}

Důležité je, že výjimka může být vyhozena hlouběji, při volání jiných metod.

Pro jeden blok try lze uvést více bloků catch, pokud očekáváte různé typy výjimek.

Zároveň můžeme vytvořit hierarchii výjimek, kde každá třída výjimky dědí od té předchozí. Jako příklad si představme jednoduchou bankovní aplikaci, která umožňuje provádět vklady a výběry:

class BankovniVyjimka extends Exception {}
class NedostatekProstredkuVyjimka extends BankovniVyjimka {}
class PrekroceniLimituVyjimka extends BankovniVyjimka {}

class BankovniUcet
{
	private int $zustatek = 0;
	private int $denniLimit = 1000;

	public function vlozit(int $castka): int
	{
		$this->zustatek += $castka;
		return $this->zustatek;
	}

	public function vybrat(int $castka): int
	{
		if ($castka > $this->zustatek) {
			throw new NedostatekProstredkuVyjimka('Na účtu není dostatek prostředků.');
		}

		if ($castka > $this->denniLimit) {
			throw new PrekroceniLimituVyjimka('Byl překročen denní limit pro výběry.');
		}

		$this->zustatek -= $castka;
		return $this->zustatek;
	}
}

$ucet = new BankovniUcet;
$ucet->vlozit(500);

try {
	$ucet->vybrat(1500);
} catch (PrekroceniLimituVyjimka $e) {
	echo $e->getMessage();
} catch (NedostatekProstredkuVyjimka $e) {
	echo $e->getMessage();
} catch (BankovniVyjimka $e) {
	echo 'Vyskytla se chyba při provádění operace.';
}

V tomto příkladu je důležité si všimnout pořadí bloků catch. Protože všechny výjimky dědí od BankovniVyjimka, pokud bychom tento blok měli první, zachytily by se v něm všechny výjimky, aniž by se kód dostal k následujícím catch blokům. Proto je důležité mít specifičtější výjimky (tj. ty, které dědí od jiných) v bloku catch výše v pořadí než jejich rodičovské výjimky.

Správné postupy

Když máte za sebou základní principy objektově orientovaného programování, je důležité se zaměřit na správné postupy v OOP. Ty vám pomohou psat kód, který je nejen funkční, ale také čitelný, srozumitelný a snadno udržovatelný.

  1. Oddělení zájmů (Separation of Concerns): Každá třída by měla mít jasně definovanou odpovědnost a měla by řešit pouze jeden hlavní úkol. Pokud třída dělá příliš mnoho věcí, může být vhodné ji rozdělit na menší, specializované třídy.
  2. Zapouzdření (Encapsulation): Data a metody by měly být co nejvíce skryté a přístupné pouze prostřednictvím definovaného rozhraní. To vám umožní měnit interní implementaci třídy bez ovlivnění zbytku kódu.
  3. Předávání závislostí (Dependency Injection): Místo toho, abyste vytvořili závislosti přímo v třídě, měli byste je „injektovat“ z vnějšku. Pro hlubší porozumění tomuto principu doporučujeme kapitoly o Dependency Injection.