9. díl - Statika v PHP

PHP Objektově orientované programování Statika v PHP American English version English version

V minulém tutoriálu o objektově orientovaném programování v PHP jsme si vysvětlili jak funguje polymorfismus a naučili se používat autoloader pro automatické načítání tříd. V dnešním dílu se seznámíme s velmi kontroverzní technikou - statikou.

Pozor na statikuPOZOR! Dnešní lekce vám ukáže statiku, tedy postupy, které v podstatě narušují objektový model. OOP je obsahuje jen pro speciální případy a obecně platí, že vše jde napsat bez statiky. Vždy musíme pečlivě zvážit, zda statiku opravdu nutně potřebujeme. Obecně bych doporučoval statiku vůbec nepoužívat, pokud si nejste naprosto jisti, co děláte. Podobně, jako globální proměnné je statika v objektovém programování něco, co umožňuje psát špatný kód a porušovat dobré praktiky. Znalosti použijte s rozvahou, na světe bude potom méně zla.

Třídní prvky

V objektově orientovaném programování jsme již trochu sběhlí a proto víme, že aplikace je složená z množiny komunikujících objektů. Objekty jsou vždy instance, vytvořené podle nějaké třídy. Každá instance je unikátní a má svůj vnitřní stav (atributy) a schopnosti (metody).

Objektově orientované programování však kromě instančních prvků umožňuje definovat i prvky třídní. Jedná se o atributy a metody, které nepatří instanci, ale patří třídě. Třídní metodu tedy voláme na třídě, místo na instanci a instanci vůbec nepotřebujeme. Takovou metodu označíme klíčovým slovem static a můžeme ji zavolat odkudkoli z programu aniž bychom měli instanci. Zkusme si to na jednoduchém příkladu.

Matematická třída

Vytvořme si ve složce "tridy" novou třídu Matematika.php. Do ní nejprve umístíme instanční metodu naDruhou(), jak jsme byli zvyklí doposud:

class Matematika
{

    public function naDruhou($zaklad)
    {
        return $zaklad *$zaklad;
    }

}

V index.php si zkusme nechat vypočítat druhou mocninu pomocí naší metody. Vytvoříme si instanci naší třídy Matematika a zavoláme na ní příslušnou metodu:

$matematika = new Matematika();
echo($matematika->naDruhou(12));

Výstupem programu je samozřejmě číslo 144, ale o ten tu teď nejde. Představte si, že v nějaké výpočetní aplikaci používáte na mnoha místech v mnoha třídách nějaké podobné pomocné výpočty. Bylo by poněkud nepraktické stále tvořit nové instance nebo si předávat instanci třídy Matematika. Z toho důvodu uděláme metodu naDruhou() statickou. Upravme třídu Matematika na tuto podobu:

class Matematika
{

    public static function naDruhou($zaklad)
    {
        return $zaklad *$zaklad;
    }

}

V index.php odstraníme vytvoření instance a metodu naDruhou() budeme volat přímo na třídě Matematika. K volání třídních prvků nepoužíváme operátor šipky (->), ale čtyřtečku (::):

echo(Matematika::naDruhou(12));

Metodu jsme zavolali bez instance. Dá se říci, že je také globální a viditelná odkudkoli, což může při špatném použití způsobit velmi špatnou čitelnost kódu (viz dále). Víme, že OOP se snaží spíše co nejvíce věcí skrývat. V tomto případě je to však v pořádku a dále v seriálu si vysvětlíme další správná i špatná užití.

Kombinace statických a instančních prvků

Třída nemusí obsahovat jen instanční nebo jen statické prvky, ale může obsahovat jejich kombinaci. Instanční prvky jsou poté přístupné jen na instanci, statické zase jen na třídě.

Číslování instancí

O statických prvcích můžeme zjednodušeně říci, že jsou společné pro všechny instance, jelikož patří té jedné třídě. Vraťme se zas k našim lidem a dejme tomu, že je budeme potřebovat číslovat. Každý člověk bude mít atribut $id, ve kterém bude unikátní číslo. První vytvořený člověk bude mít $id=1, druhý $id=2 a tak dále. Kde ale uchovávat kolik lidí jsme již vytvořili?

Pokud známe statiku, není nic jednoduššího než přidat do třídy Clovek statický atribut pocetLidi, nastavený na 0. Třída si tedy pamatuje kolik lidí bylo vytvořeno. Je to i logicky správně, jelikož atribut je sdílený mezi všemi instancemi a proto je na třídě. V konstruktoru člověka poté tento statický atribut vždy zvýšíme o 1 a do instanční proměnné $id dosadíme toto číslo.

Třída Clovek.php bude vypadat s touto malou úpravou následovně:

class Clovek
{

    public $jmeno;
    public $prijmeni;
    public $vek;
    private $unava = 0;
    public $id;
    private static $pocetLidi = 0;

    public function __construct($jmeno, $prijmeni, $vek)
    {
        $this->jmeno = $jmeno;
        $this->prijmeni = $prijmeni;
        $this->vek = $vek;
        self::$pocetLidi++;
        $this->id = self::$pocetLidi;
    }

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

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

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

    protected function celeJmeno()
    {
        return $this->jmeno . ' ' . $this->prijmeni;
    }

    public function __toString()
    {
        return $this->jmeno . ' - ' . $this->id;
    }

}

Při bližším pohledu vidíme 2 nové atributy, instanční $id a třídní $pocetLidi.

V konstruktoru inkrementujeme (zvýšíme o 1) proměnnou $pocetLidi a hodnotu dosadíme nové instanci do jejího $id. Ke statickým prvkům se uvnitř třídy přistupuje pomocí klíčového slova self. Má podobný význam jako $this, které uchovávalo aktuální instanci, self uchovává aktuální třídu. Matoucí může být, že se za čtyřtečku na rozdíl od $this píše dolar.

Poslední změna spočívá v metodě __toString(), kde vracíme i ID, abychom jednoduše viděli, že nám jejich přidělování funguje.

V index.php poupravíme cyklus tak, aby místo zdravení lidi vypisoval. Pro úplnost dodávám i jejich tvoření:

$lide = array();
$lide[] = new Clovek('Karel', 'Novák', 30);
$lide[] = new Javista('Jan', 'Nový', 24, 'Eclipse');
$lide[] = new Clovek('Josef', 'Nový', 50);
$lide[] = new Javista('Tomáš', 'Marný', 28, 'NetBeans');
$lide[] = new Clovek('Marie', 'Nová', 32);

foreach ($lide as $clovek)
{
        echo($clovek . '<br />');
}

Výstup programu:

Statika v PHP – číslování instancí

Vidíme, že statický atribut $pocetLidi opravdu náleží třídě Clovek. Když PHP třídu poprvé načte, vytvoří atribut a nastaví do něj hodnotu 0. Statický atribut na třídě Clovek v paměti přetrvává do skončení skriptu bez ohledu na to, zda existují nějaké instance lidí nebo ne. S jeho hodnotou můžeme manipulovat z instančních metod (zde z konstruktoru). Ze statické metody nelze volat metoda instanční, jelikož k tomu potřebujeme instanci :) Naopak to tedy nelze.

Bez statiky by se tento příklad řešil pomocí objektu, který by lidi vyráběl. Takovým objektům se říká továrny, ale o tom zas někdy jindy.

Statika je velmi důležitá, velmi používaná a často velmi mylně chápaná programátorská technika. Z toho důvodu ji budou věnované ještě 2 následující díly. Řekneme si kdy ji používat a kdy nikoli. Budete na ni narážet často a je velmi důležité, abyste chápali jak funguje. Příště budeme pokračovat a vysvětlíme si jak se v PHP používají konstanty. Soubory z dnešní lekce jsou jako vždy ke stažení v archivu níže.


 

Stáhnout

Staženo 362x (1.59 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (4)

Č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 (13 hlasů) :
55555


 



 

 

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

Avatar
Martin Konečný (pavelco1998):

Záleží na tom, jak chceš ty atributy používat. ID by nejspíše private být mělo, ale budeš na to muset pak udělat nějaký getter.
V určitých případech je ale snadnější definovat ty atributy jako public (ať třeba pro kratší zápis $obj->atribut než přes $obj->getAtribut(), tak i když přiřazuješ hodnoty dynamicky, např. $obj->$atribut = $hodnota).

 
Odpovědět  +1 31.7.2014 0:32
Avatar
Mazwor
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
Mazwor:

Nojo, to je fakt, pořád jsem uvažoval jen nad úpravou toho ID, ale vůbec mi nedošlo, že tím zamezím i možnosti toto ID někde vypsat. Pak by samozřejmě použití public dávalo smysl, to je zřejmé. Díky ;).

Ještě mě teď při zmínění toho getteru napadlo... Bylo by "programátorsky správné" třeba vytvořit i na práci s private ID public function v nějakém takovém stylu (případně s použitím return namísto echo) nebo je to zbytečně navíc?

public function vypisID() {
    echo($this->id);
}
Editováno 31.7.2014 1:02
Odpovědět 31.7.2014 1:02
Pořádek je pro blbce, inteligent ovládá chaos. :D
Avatar
Odpovídá na Mazwor
Martin Konečný (pavelco1998):

Obvykle je lepší, než přímo něco vypisovat, něco jen vrátit. To je účel toho getteru.

public function getId()
{
  return $this->id;
}

V aplikaci se to ID uživatele používá poměrně často, takže když ho budeš potřebovat, jednoduše zavoláš ten getter.

Nevím, jak daleko v OOP jsi, ale aby se ve třídě zamezilo množství setterů a getterů, je možné si ty údaje naházet do pole a pak je brát pomocí magické metody __get().
Také si nejsem jistý, jak se to nejčastěji řeší, ale když ta data taháš z databáze (což pravděpodobně budeš), je lepší si to uložit právě jako pole - vytáhneš ta data všechny naráz a ne po jednom.
Pak máš např. asociativní pole ve tvaru:

$data = array(
  "id" => 123,
  "jmeno" => "Martin",
  "email" => "muj@mail.cz"
);

Nechci se tady rozepisovat, jak to natahat z DB apod., protože by to bylo od tématu, ale klidně napiš PM.

 
Odpovědět 31.7.2014 1:40
Avatar
jip123
Člen
Avatar
jip123:

Seriál je celkem dobrý, ale narazil jsem v kódu na spl_autoload_re­gister("nacti­Tridu");, nebylo vysvětleno,nechápu jak to funguje.

 
Odpovědět 3.9.2014 17:58
Avatar
Jiří Gracík
Redaktor
Avatar
Odpovídá na jip123
Jiří Gracík:

Tahle funkce zaregistruje třídu, kterou jí předáš v parametru jako autoloader. Autoloader se zavolá, pokud budeš zakládat objekt, jehož třída není ještě načtená. Většinou se podle názvu třídy načte soubor, který ji obsahuje.

Odpovědět 3.9.2014 18:01
Creating websites is awesome till you see the result in another browser ...
Avatar
Danny
Člen
Avatar
Danny:

Ahoj, prosím někoho, jestli by mi řekl zda to chápu vše dobře.
Zkusil jsem si metodu "validniHeslo()" upravit tak aby ta logika byla v ní a ne v indexu a mám to tedy takto. Funguje to, ale spíš by mě zajímalo jestli to chápu správně. Je to statická metoda, tím pádem bez instance a přímo se odkazuju na třídu v které ta metoda je, tady "Clovek"? Potom to teda mám ve třídě Clovek takto:

const DELKA_HESLA = 5;


public static function validniHeslo($heslo)
    {
        if (mb_strlen($heslo) >= self::DELKA_HESLA)
        $hlaska = "Heslo je v pořádku";
        else $hlaska="Heslo musí být dlouhé minimálně 5 znaků";
        return $hlaska;

    }

a v indexu takto:

echo Clovek::validniHeslo($heslo);

Tak by mě jenom zajímalo jestli to tak je správně a správně to chápu.Předem díky za odpověď :)

 
Odpovědět 19.7.2015 22:19
Avatar
Odpovídá na Danny
Martin Konečný (pavelco1998):

Obvykle je lepší, aby metody nevracely přímo určitý text, ale jen stav. Tím je myšlena např. informace o chybě (číslo/klíč chyby), bool hodnota TRUE/FALSE apod.
Ve tvém případě něco jako

public static function validniHeslo($heslo)
{
    if (mb_strlen($heslo) >= self::DELKA_HESLA) {
        return TRUE;
    }

    return FALSE;
}

Chceš provést víc validací naráz a zjistit, kde to škončilo chybou?

class User
{

    const PASS_LENGTH = 5;

    // atributy a metody

}

class PasswordValidator
{

    const ERR_PASSWORD_SHORT = 1,
        ERR_NO_UPPERCASE_LETTERS = 2,
        ERR_NO_NUMBERS = 3;

    const PASS_OK = 0;

    public static function checkPassword($p)
    {
        if (mb_strlen($p) < User::PASS_LENGTH) {
          return self::ERR_PASSWORD_SHORT;
        }
        if (!preg_match("kontrola velkých písmen", $p)) {
          return self::ERR_NO_UPPERCASE_LETTERS;
        }
        if (!preg_match("kontrola čísel", $p)) {
          return self::ERR_NO_NUMBERS;
        }

        return self::PASS_OK;
    }

}


$p = $_POST["password"];
$result = PasswordValidator::checkPassword($p);
if ($result !== PasswordValidator::PASS_OK) {
    switch ($result) {
        case PasswordValidator::ERR_PASSWORD_SHORT:
            $msg = "heslo je příliš krátké";

        case PasswordValidator::ERR_NO_UPPERCASE_LETTERS:
            $msg = "heslo musí obsahovat velká písmena";

        case PasswordValidator::ERR_NO_NUMBERS:
            $msg = "heslo musí obsahovat čísla";
    }

    echo "V ověření hesla se vyskytla chyba: {$msg}";
}

To je ale ošklivý, co? Co tohle?

class PasswordValidator
{

    public static function checkPassword($p)
    {
        if (mb_strlen($p) < User::PASS_LENGTH) {
          throw new Exception("heslo je příliš krátké");
        }
        if (!preg_match("kontrola velkých písmen", $p)) {
          throw new Exception("heslo musí obsahovat velká písmena");
        }
        if (!preg_match("kontrola čísel", $p)) {
          throw new Exception("heslo musí obsahovat čísla");
        }
    }

}


$p = $_POST["password"];

try {
   PasswordValidator::checkPassword($p);

   // další akce
} catch (Exception $e) {
    echo "Někde se stala chyba: {$e->getMessage()}";
}

Zde by opět pro určité případy nebylo vhodné, aby výjimka obsahovala přímo text. Může obsahovat klíč (podle kterého se text přeloží), kód chyby, případně i vyhazovat jiný druh výjimky dle druhu chyby (to mi ale přijde trochu overkill).

 
Odpovědět  +3 20.7.2015 1:03
Avatar
Danny
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
Danny:

WAU...tak takhle daleko ještě nejsem :) jsem u počítadla přístupů, ale asi tomu rozumím :)
takže se pak volají vlastně konstanty, které přiřadím k nějaký validaci., paráda.

Každopádně díky moc a hned jak nastuduju asi vyjímky jestli chápu, tak se k tomu vrátím.
Spíš jsem se chtěl ujistit jestli chápu princip :) díky

 
Odpovědět 20.7.2015 1:22
Avatar
loading84
Člen
Avatar
loading84:

Já nevím proč, ale musím mít nastavené

private static $pocetLidi=-1;

jinak mi to čísluje od dvojky.

 
Odpovědět 12.10.2015 14:38
Avatar
Jan Zamecnik
Člen
Avatar
Odpovídá na loading84
Jan Zamecnik:

Trochu pozdě na odpověď, ale bude to možná tím, že postupně přidáváš kód během učení, tak jak to dělám já a nad definováním pole

$lide = array();

jsi již vytvořil jednu instanci Člověka nebo Javisty. Mě to takhle počítalo od 3 a zjistil jsem, že jsem již dříve vytvořil jednoho Člověka a jednoho Javistu. Ti se počítají taky, i když nejsou součásti pole.

 
Odpovědět 16. září 15:56
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 17. Zobrazit vše