IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 6 - Referenční a primitivní datové typy v PHP

V minulé lekci, První objektová komponenta v PHP - Galerie obrázků, jsme si vytvořili první objektovou komponentu, jednalo se o galerii obrázků.

Začínáme pracovat s objekty a objekty jsou referenčními datovými typy, které se v některých ohledech chovají jinak, než tzv. typy primitivní. Je důležité, abychom přesně věděli, co se uvnitř programu děje, jinak by nás v budoucnu mohlo leccos překvapit. Této problematice se budeme v dnešním PHP tutoriálu věnovat.

Primitivní datové typy

Proměnné primitivního datového typu jsme používali doposud, jedná se např. o číslo nebo o pole. I když primitivní typy v jiných jazycích označují pouze jednoduché struktury, jako jsou např. čísla nebo znaky (od toho název primitivní), v PHP je primitivním typem i pole nebo textový řetězec. Primitivní typy jsou vlastně všechny datové typy kromě objektů.

Kopírování hodnoty

Pokud někam předáme primitivní typ, jeho hodnota se vždy zkopíruje. Ačkoli to určitě víte, zkusme si to. Založme si bokem nějaký skript a vytvořme v něm nové pole s jednou hodnotou 56:

$a = array(56);

Pole $a nyní přiřaďme do pole $b:

$b = $a;

Do pole $b přidejme ještě další prvek:

$b[] = 28;

Nakonec vypišme obě pole:

print_r($a);
print_r($b);

Když si nyní zobrazíme zdrojový kód výsledné stránky, výstup bude pravděpodobně tak, jak jste ho očekávali:

Kopírování hodnoty
localhost

Ačkoli jsme změnili obsah proměnné $b, její původní hodnota zůstala zkopírovaná v proměnné $a. Výsledkem jsou tedy 2 různá pole, první jen s jednou hodnotou, druhé obsahující původní hodnotu plus tu novou.

Referenční datové typy

S referenčními datovými typy, nebo chcete-li s objekty, je při předávání pracováno jiným způsobem. Zkusme si vytvořit podobnou situaci, ale místo čísel ukládejme objekty.

K tomu nám perfektně poslouží instance našich lidí. Vytvořme si analogicky proměnnou $a s instancí člověka:

require_once('tridy/Clovek.php');
$a = new Clovek('Jan', 'Novák', 30);

Nyní opět přiřaďme do proměnné $b obsah proměnné $a:

$b = $a;

Opět po přiřazení změňme hodnotu proměnné $b, zde tak, že uživateli v proměnné $b změníme věk:

$b->vek = 50;

Opět vypíšeme obě proměnné:

print_r($a);
print_r($b);
class Clovek
{

    private $unava = 0;

    public function __construct(public string $jmeno, public string $prijmeni, public int $vek) {}

    public function spi(int $doba): void
    {
        $this->unava -= $doba * 10;
        if ($this->unava < 0)
            $this->unava = 0;
    }

    public function behej(int $vzdalenost): void
    {
        if ($this->unava + $vzdalenost <= 20)
            $this->unava += $vzdalenost;
        else
            echo('Jsem příliš unavený.');
    }

    public function pozdrav(): void
    {
        echo('Ahoj, já jsem ' . $this->jmeno);
    }

    public function __toString(): string
    {
        return $this->jmeno;
    }
}

A hle, něco je jinak. Měli byste vidět takovýto výsledek:

Člověk
localhost

Rekapitulace

Vytvořili jsme si instanci člověka a tu jsme uložili do proměnné $a. Instance a proměnná jsou však 2 rozdílné věci a to z toho důvodu, že instance není přímo uložená v proměnné.

Instance objektů jsou uložené v paměti, které se říká halda (anglicky heap). Tato paměť není velikostně omezena (vejde se do ní kolik povolíte nebo kolik máte RAM). Tuto paměť si můžeme jednoduše představit jako velkou haldu objektů. V proměnné $a je potom uložená pouze tzv. reference, která ukazuje na instanci na haldě.

Jakmile jsme tedy do proměnné $b přiřadili proměnnou $a, řekli jsme, že $b ukazuje na tu samou instanci, jako $a. Celou situaci jsem pro vás vizualizoval, aby byla lepší k pochopení:

Halda v PHP - Objektově orientované programování (OOP) v PHP

Na obrázku je znázorněna halda s objekty a proměnné, které obsahují referenci na tyto objekty. Vidíme, že proměnné $a i $b ukazují na ten samý objekt. Z toho důvodu se změnil obsah proměnné $a ve chvíli, kdy jsme změnili $b. Člověka tam máme na rozdíl od předchozí situace s poli pouze jednoho.

Práce s referencemi je přirozenější a mnohdy i rychlejší. Objekt stačí vytvořit jednou a poté ho referencí předávat tam, kde je potřeba. Když chcete v PHP použít pole na více místech, vždy se jeho obsah kopíruje, což může být při větších datech nevýhodné. Předání obrovského objektu je naprosto nenáročná operace, jelikož se předává jen reference. Časem jistě uznáte i to, že je to pro člověka přirozenější a aplikace se potom i lépe navrhuje. V realitě se nám totiž věci také neklonují (většinou :) ).

Většina programovacích jazyků pracuje referenčně i s poli a textovými řetězci. Je to hlavně z toho důvodu, že primitivní typy jsou ukládané v paměti zvané zásobník (anglicky stack). Ta je velmi rychlá, ale její velikost je také velmi omezená. Složitější datové typy (obyčejně s neomezenou délkou) jsou vždy ukládány do haldy a ve stacku je umístěna pouze reference na ně. PHP tento fakt pravděpodobně vnitřně obchází a jako interpreter zachovává chování primitivních datových typů i u polí, i když jsou fyzicky také uložená na haldě.

Vynucení reference

Jak již bylo řečeno, PHP pracuje se vším kromě objektů jako s primitivním typem a to tak, že v případě předání hodnotu vždy zkopíruje. To je někdy výhodné, ale někdy zase nevýhodné. Z toho důvodu existuje možnost, jak předat proměnné primitivního typu (např. pole) referencí. Hned na úvod bych chtěl říci, abyste se použití těchto hacků spíše vyvarovali, jelikož mění princip jakým PHP funguje ve výchozím stavu a když na to zapomenete, může vás to někdy hodně překvapit a způsobit spoustu nepříjemností. Uvádím je zde tedy spíše pro úplnost.

Předání proměnné parametrem

Představte si, že máte funkci, po které chcete, aby něco přidala do pole, které předáte v jejím parametru. Naivní implementace by mohla vypadat takto:

// Tento kód nefunguje
function pridej(array $pole, mixed $prvek): void
{
    $pole[] = $prvek;
}

Již pro vás však nebude překvapením, že tento kód do pole $a nic nepřidá:

$a = array(1, 2, 3);
pridej($a, 4);
print_r($a);

Je to samozřejmě z toho důvodu, že pole $a se zkopíruje do proměnné $pole a do té se přidá další prvek. Původní proměnná $a se však vůbec nezmění. U funkce můžeme vynutit referenční přístup (jako u objektů) pomocí operátoru &:

function pridej(array &$pole, mixed $prvek): void
{
    $pole[] = $prvek;
}

Kód bude nyní fungovat, nicméně je to spíše takový hack a není to příliš pěkné. Kdybychom napsali kód objektově (všimněte si, že je to funkce a ne metoda), tak bychom takové věci nepotřebovali. Pole by totiž bylo atributem tohoto objektu a nebyl by žádný problém s tím ho modifikovat. Navíc bychom si ušetřili atribut:

public function pridej(mixed $prvek): void
{
    $this->pole[] = $prvek;
}

Vidíme, jak objekty zjednodušují situaci a předchází nepřehlednému kódu. Neobjektovým řešením by mohlo být vrátit modifikované pole pomocí return a poté ho přiřadit zpět do proměnné $a. Celé pole se nám tak ale již 2x zkopíruje, takže to není nejvýhodnější.

Modifikace pole v cyklu

Vynutit referenci se nám může hodit v případě, kdy projíždíme pole foreach cyklem a potřebujeme měnit hodnoty. Tento kód nebude fungovat:

// Tento kód nebude fungovat
$a = array(1, 2, 3);
foreach ($a as $prvek)
{
    $prvek++;
}
print_r($a);

Prvek pole se zkopíruje (pokud to není objekt) do proměnné $prvek a my měníme až tu, k samotnému prvku se nedostaneme. S vynucením reference by to vypadalo takto:

$a = array(1, 2, 3);
foreach ($a as &$prvek)
{
    $prvek++;
}
print_r($a);

Prvek nyní ukazuje na původní prvek v poli a mění se tedy pole, nikoli jen zkopírovaný prvek. Řešení je již méně matoucí než u funkce a párkrát jsem ho použil, stále jde však obejít přes for cyklus nebo vestavěné funkce, které nám PHP pro práci s poli nabízí.

V následujícím cvičení, Řešené úlohy k 4.-6. lekci OOP v PHP, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Předchozí článek
První objektová komponenta v PHP - Galerie obrázků
Všechny články v sekci
Objektově orientované programování (OOP) v PHP
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 4.-6. lekci OOP v PHP
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
168 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity