6. díl - Referenční a primitivní datové typy v PHP

PHP Objektově orientované programování Referenční a primitivní datové typy v PHP American English version English version

V minulém tutoriálu o objektově orientovaném programování v PHP 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.

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:

Primitivní datové typy v PHP

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 + 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('Karel', '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);

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

Referenční datové typy v PHP

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

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 :)).

Pozn.: 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 (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($pole, $prvek)
{
        $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(&$pole, $prvek)
{
        $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($prvek)
{
        $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í.

Příště si uvedeme dědičnost a polymorfismus, které patří spolu se zapouzdřením k základním pilířům OOP.


 

  Aktivity (2)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (17 hlasů) :
4.941184.941184.941184.941184.94118


 



 

 

Komentáře
Zobrazit starší komentáře (9)

Avatar
d4rkw34v3r
Člen
Avatar
d4rkw34v3r:

Ano, dá.

<?php

require_once('tridy/pole.php');

echo '<br />';
echo '<br />';
$pole1 = new Pole([1,2,3,4,5,6]);
$pole1->zobraz();

 ?>

a třída:

class Pole
{
    /** @var array */
    private $prvky;


    public function __construct(array $prvky) // pokud to má být pouze pole, lze uvést před argumentem hint
    {
        $this->prvky = $prvky;
    }


    public function pridej($prvek)
       {
        $this->prvky[] = $prvek;
       }

    public function zobraz()
       {
       printf_r($prvky);
       }
    }
Editováno 11.10.2015 15:58
 
Odpovědět 11.10.2015 15:57
Avatar
Martin Konečný (pavelco1998):

Parametr v konstruktoru ani nemusí být pole.

$pole = new Pole(1, 2, 3, 4, 5);

class Pole
{

        public function __construct()
        {
                $this->prvky = func_get_args();
        }

}
 
Odpovědět 11.10.2015 18:05
Avatar
loading84
Člen
Avatar
Odpovídá na d4rkw34v3r
loading84:

Nefunguje mi to. A to jsem ještě našel jeden kod a taky mi nefunguje.

class Foo
{
    private $data = array();

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function __get($vlastnost)
    {
        return $this->data[$vlastnost];
    }
}

$foo = new Foo(array("bar" => "baz"));
echo $foo->bar; // vypíše baz
 
Odpovědět 11.10.2015 18:14
Avatar
loading84
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
loading84:

Tak ani s tímhle kódem mi to nefunguje. :(.

Přece je to docela časté předávat objektu nějaké pole hodnot.

 
Odpovědět 11.10.2015 18:18
Avatar
loading84
Člen
Avatar
loading84:

Objeví se mi bílá stránka. http://www.zahyb.cz/oop/pole

jsem asi lama

Editováno 11.10.2015 18:43
 
Odpovědět 11.10.2015 18:42
Avatar
loading84
Člen
Avatar
loading84:

Tenhle kod teda funguje a to vlozeni cisel ne.

class Foo
{
    private $data = array();

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function __get($vlastnost)
    {
        return $this->data[$vlastnost];
    }
}

$foo = new Foo(array("bar" => "baz"));
echo $foo->bar; // vypíše baz
 
Odpovědět 11.10.2015 19:30
Avatar
loading84
Člen
Avatar
loading84:

Pro lepší pochopení reference:

function pridej($pole, $prvek)
{
    $pole[] = $prvek;
    print_r($pole);
}


$a = array(1, 2, 3);
pridej($a, 4);
echo '<br />';
print_r($a);
 
Odpovědět 9.11.2015 15:57
Avatar
saša harvan
Člen
Avatar
saša harvan:
<?php
function pridej(&$pole, $prvek)
{
        $pole[] = $prvek;

  }

function odeber(&$pole,$prvek)
{
unset($pole[$prvek]);
}


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

pridej($a, 5);
print_r($a);

odeber($a,1);
print_r($a);
?>

kdzy pridat at je tu i odebrat :) asi je lepsi reseni ale nic jinyho me nenapadlo, jsem amater tak sry... jinalk moc pekny stranky

 
Odpovědět 22.12.2015 16:09
Avatar
Jan Zamecnik
Člen
Avatar
Jan Zamecnik:

Rozšířil jsem třídu Clovek o vlastnosti. Může jich mít více, ale každou pouze jednou, jde je přidávat i odebírat.
Zde je kód:.
public $vlastnosti = array();
public function pridej_vlastnos­t($vlastnost) {
if (array_search($vlas­tnost, $this->vlastnosti) !== false) {
echo('');
echo ('Tuhle vlastnost již ' . $this->jmeno . ' má.');
} else {
$this->vlastnosti[] = $vlastnost;
}
}

public function vypis_vlastnosti() {
echo('');

print_r($this->vlastnosti);
}

public function vymaz_vlastnos­t($vlastnost) {
echo('');
if (array_search($vlas­tnost, $this->vlastnosti) === false) {
echo ('Tuhle vlastnost ' . $this->jmeno . ' nemá, není co vymazat.');
} else {
$index = array_search($vlas­tnost, $this->vlastnosti);
array_splice($this->vlastnosti, $index, 1);
}

 
Odpovědět 15. září 14:51
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 19. Zobrazit vše