Ú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 (proměnnou) $vek a dále konstruktor, který nastavuje tuto vlastnost. Metoda kolikJeTiLet() pak umožňuje přístup k věku osoby.

Pseudoproměnná $this se používá uvnitř třídy pro přístup k vlastnostem a metodám objektu.

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.

Příjemné je, že definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit:

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

Pro úplnost, kromě konstruktorů mohou mít objekty i destruktory (metoda __destruct), které se zavolají před tím, než je objekt uvolněn z paměti.

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 vypisInformace()
	{
		echo "Věk: {$this->vek} let\n";
	}
}

class Student extends Osoba
{
	private $obor;

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

	function vypisInformace()
	{
		parent::vypisInformace();
		echo "Obor studia: {$this->obor} \n";
	}
}

$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. A obdobně i metodu vypisInformace() předka před vypsáním informací o studentovi.

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.

Všimněte si, že metody vypisInformace() ve třídách Osoba a Student vypisují trochu jiné informace. A můžeme doplnit další třídy (například Zamestnanec), které budou poskytovat další implementace této metody. Schopnost objektů různých tříd reagovat na stejnou metodu různými způsoby se nazývá polymorfismus:

$osoby = [
	new Osoba(30),
	new Student(20, 'Informatika'),
	new Zamestnanec(45, 'Ředitel'),
];

foreach ($osoby as $osoba) {
	$osoba->vypisInformace();
}

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:

interface Hlidac
{
	function hlidejDum();
}

class Pes implements Zvire, Hlidac
{
	public function vydejZvuk()
	{
		echo 'Haf';
	}

	public function hlidejDum()
	{
		echo 'Pes bedlivě střeží dům';
	}
}

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.

Jak se vlastně liší rozhraní a abstraktních tříd? Abstraktní třídy mohou obsahovat jak abstraktní, tak konkrétní metody, zatímco rozhraní pouze definují, jaké metody musí třída implementovat, ale neposkytují žádnou implementaci. Třída může dědit jen od jedné abstraktní třídy, ale může implementovat libovolný počet rozhraní.

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.

Některé typy nelze v PHP přímo zapsat. V takovém případě se uvádí v phpDoc komentáři, což je standardní formát pro dokumentaci PHP kódu začínající /** a končící */. Umožňuje přidávat popisy tříd, metod a tak dále. A také uvádět komplexní typy pomocí tzv. anotací @var@param a @return. Tyto typy pak využívají nástroje pro statickou analýzu kódu, ale samotné PHP je nekontroluje.

class Seznam
{
	/** @var array<Osoba>  zápis říká, že jde o pole objektů Osoba */
	private array $osoby = [];

	public function pridatOsobu(Osoba $osoba): void
	{
		$this->osoby[] = $osoba;
	}
}

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í elegantně zpracovávat chyby a neočekávané situace v našem kódu. Jsou to objekty, které nesou informace o chybě nebo neobvyklé situaci.

V PHP máme vestavěnou třídu Exception, která slouží jako základ pro všechny výjimky. 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ž v kódu nastane chyba, můžeme „vyhodit“ výjimku pomocí klíčového slova throw.

function deleni(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Dělení nulou!');
	}
	return $a / $b;
}

Když funkce deleni() dostane jako druhý argument nulu, vyhodí výjimku s chybovou zprávou 'Dělení nulou!'. Abychom zabránili pádu programu při vyhození výjimky, zachytíme ji v bloku try/catch:

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

Kód, který může vyhodit výjimku, je zabalen do bloku try. Pokud je výjimka vyhozena, provádění kódu se přesune do bloku catch, kde můžeme výjimku zpracovat (např. vypsat chybovou zprávu).

Po blocích try a catch můžeme přidat nepovinný blok finally, který se provede vždy, ať už byla výjimka vyhozena nebo ne (dokonce i v případě, že v bloku try nebo catch použijeme příkaz return, break nebo continue):

try {
	echo deleni(10, 0);
} catch (Exception $e) {
	echo 'Výjimka zachycena: '. $e->getMessage();
} finally {
	// Kód, který se provede vždy, ať už byla výjimka vyhozena nebo ne
}

Můžeme také vytvořit vlastní třídy (hierarchii) výjimek, které dědí od třídy Exception. 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;
	}
}

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

$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.

Iterace

V PHP můžete procházet objekty pomocí foreach smyčky, podobně jako procházíte pole. Aby to fungovalo, objekt musí implementovat speciální rozhraní.

První možností je implementovat rozhraní Iterator, které má metody current() vracející aktuální hodnotu, key() vracející klíč, next() přesouvající se na další hodnotu, rewind() přesouvající se na začátek a valid() zjišťující, zda ještě nejsme na konci.

Druhou možností je implementovat rozhraní IteratorAggregate, které má jen jednu metodu getIterator(). Ta buď vrací zástupný objekt, který bude zajišťovat procházení, nebo může představovat generátor, což je speciální funkce, ve které se používá yield pro postupné vracení klíčů a hodnot:

class Osoba
{
	public function __construct(
		public int $vek,
	) {
	}
}

class Seznam implements IteratorAggregate
{
	private array $osoby = [];

	public function pridatOsobu(Osoba $osoba): void
	{
		$this->osoby[] = $osoba;
	}

	public function getIterator(): Generator
	{
		foreach ($this->osoby as $osoba) {
			yield $osoba;
		}
	}
}

$seznam = new Seznam;
$seznam->pridatOsobu(new Osoba(30));
$seznam->pridatOsobu(new Osoba(25));

foreach ($seznam as $osoba) {
	echo "Věk: {$osoba->vek} let \n";
}

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.
verze: 4.0