PHP_04 - Objekty, třídy a dědičnost

1. Obsah

  1. Obsah
  2. Úvod
  3. Definice a instance třidy
  4. Metody
  5. Konstruktor
  6. Řízení přistupu k metodám a proměnným
  7. Skládání
  8. Dědičnost
  9. Úkol
  10. Zdroje a citace

2. Úvod

Na úvod si řeknem něco málo teorie o objektově orientovaném programování (dále jen OOP), abychom rozuměli základním termínům jako je objekt, třída, atribut, metoda a podobně.

Co je to OOP a proč by mě mělo zajímat? Je to jeden ze stylů (neboli paradigma) imperativního (neboli „podmínky a cykly“) programování, jež pracuje se základní jednotkou zvanou objekt. Objekt je něco, co dokáže udržovat svůj stav a interagovat s okolím zasíláním a přijímáním zpráv. OOP vzniklo jako reakce na stále se zvyšující složitost programů s cílem usnadnit jejich psaní.

Člověka by mělo zajímat hlavně proto, že dnes je to majoritní styl programování, a pokud chce použít některé knihovny jako je Zend Framework (popis na Wikipedii), či dnes stále populárnější Nette (opět popis na Wikipedii), tak se prostě bez alespoň základních znalostí v OOP neobejde.

Přesuňme se teď trošku víc k programování. Objektem je datová struktura se sadou atributů (proměnných) a množstvím funkcí, které s daty pracují. Funkce přiřazené k objektům se v OOP nazývají metody. Data i obslužné metody jsou v objektu zapouzdřeny a navenek ukazují jen svá rozhraní (interfaces). Je dobré si uvědomit, že zapouzdřenost je jednou ze základních vlastností objektů - nevidíme a nevíme, co se děje uvnitř, což nám dovoluje dosahovat maximálního nadhledu (abstrakce).

Definici objektu je možno zdědit a vytvořit tak nový typ objektu, který může ke stávajícím atributům přidávat nějaké nové vlastnosti, stejně tak jako dodefinovávat (nebo předefinovávat!) zděděné metody.

Třída je šablonou objektu. Lze si ji představovat jako formičku, do které se „nalejou“ data a vznikne instance (výskyt) objektu.

Abychom mohli vytvářet nějaké objekty, musíme si nejprve připravit patřičné „formy“, tj. třídy. Třída se definuje pomocí příkazu class. V našem příkladu se pokusíme implementovat třídu na škole. Každá třída musí mít nějaký název, který se píše za klíčové slovo class. Naši třídu nazveme prozaicky CStudent.


3. Definice a instance třidy

Příklad definice třídy:
Název třídy může být buď posloupnost znaků vyhovujících regulárnímu výrazu [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* (řečí smrtelníků: písmeno anglické abecedy, podtržítko či nějaký znak z horní řady ASCII následovaný žádným nebo více písmeny anglické abecedy nebo čísly, podtržítky či znaky z horní řady ASCII), nebo proměnná. Jedná-li se o proměnnou, převede se nejdříve obsah této proměnné na řetězec a jako název třídy se vezme takto získaná hodnota viz Přiklad 3.2.
Příklad 3.1:

    class CStudent {
	    var $prijmeni, $jmeno;
	    var $rocnik = 4, $skupina, $kat_cislo;
    }

Příklad 3.2:

    $trida = "Trida";
    $objekt = new $trida;
    echo get_class($objekt); // vytiskne Trida
    }

Vězte, že funkce get_class() vrací název třídy daného objektu.

Příklad instance (zjednodušený jste již mohli videt v Přikladu 3.2):
Příklad 3.3:

    $student = new CStudent; // $student je instancí třídy CStudent

Přístup k proměnným objektu $student:
(ročník jsme inicializovali již v definici třidy CStudent)
Příklad 3.4:

    // Naplnime studenta Jara Cimrman z katalogovym cislem 13 a patricim do skupiny AJ1
    $student->jmeno = "Jára";
    $student->prijmeni = "Cimrman";
    $student->skupina = "AJ1";
    $student->kat_cislo = 13;

Poznámka: Třídy můžete používat také jako obyčejný datový typ záznam (struct v C, record v Pascalu), nadefinujete-li jim jenom atributy, ale žádné metody. Kdo by se ale takto okrádal o sílu OOP?


4. Metody

Jak jsme již zmínili v kapitole 2., tak Metody jsou vlastne Funkce třídy.
Příklad 4.1: - vytvoření metody

    class CStudent {
	    var $prijmeni, $jmeno;
	    var $rocnik = 4, $skupina, $kat_cislo;

	    function tisk () {
		    echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina;
	    } // END OF: function Tisk ()
    }// END OF: class CStudent  

Seznam a vysvětlení nových použitých chování a vlastností.

Příklad 4.2: - volání metody

    $student = new CStudent;
    $student->jmeno = "Jára";
    $student->prijmeni = "Cimrman";
    $student->skupina = "H";
    $student->kat_cislo = 13;
    
    // zavoláme metodu tisk
    $student->tisk();

Příklad 4.3: - metoda s parametrem

    class CStudent {
      var $prijmeni, $jmeno;
      var $rocnik = 4, $skupina, $kat_cislo;

      function tisk ($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina;
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno;
	  	  } // END OF: viceradkovy vypis
      } // END OF: function tisk ($jeden_radek = true)
    } // END OF: class CStudent

Ještě nám chybí jeden speciální druh metody a to je statická metoda. Tyto metody nevyžadují instancování celé třídy (tedy zavolání na třídu new, ale mohou byt volány "jen tak" samy o sobě. Ke statickým metodám se nepřistupuje pomocí operátorui -> ale pomocí "čtyřtečky" ::, tedy dvou znaku dvojtečeky za sebou. Ale nejlepší to zase bude cele ukázat na příkladě. Tak si představte, že potřebujeme třídu CMatematika, která bude obsahovat základní matematické funkce jako sinus, kosinus, konstantu pi a podobně.
Příklad 4.4: - staticka metoda

    class CMatematika {
      static public $pi = 3.141592657; // verejna promena
      
      static public function getPi () {
      // funkce vrati hodnotu konstanty Pi
        return 3.141592657; //  POZOR nelze volat $this->get_pi, trida neni instancovana! 
      } // END OF: public function getPi () 
      
      static public function sin($cislo) {
      // funkce vrati hodnotu sinus z $cisla, ktere musi byt uvedeno v radianech
        return sin($cislo);
      } // END OF: public function sinus($cislo)
      
      static public function cos($cislo) {
      // funkce vrati hodnotu cosinus z $cisla, ktere musi byt uvedeno v radianech
        return cos($cislo);
      } // END OF: public function sinus($cislo)
       
    } // END OF: class CMatematika

    
    // a ted přímo k vlastnímu použití
    echo 'Hodnota PI je: '.CMatematika::getPi()."<br />\n";
    echo 'Hodnota sin(60°) je: '.CMatematika::sin(deg2rad(60))."<br />\n"; // a cosinus obdobne ... 
    echo 'Jiny zpusob volani PI'.CMatematika::$pi."<br />\n"; // staticke volani promene $pi

Seznam a vysvětlení nových použitých chování a vlastností.


5. Konstruktor

Instancování (aneb vytvoření instance třídy, aneb vytvoření objektu dle třídy) a inicializace se dají smrsknout do jednoho volání. K takovým účelům slouží konstruktor. Pomocí konstruktoru můžeme například ihned po vytvoření instance inicializovat hodnoty počáteční hodnoty proměnných, což se může velice hodit.

Příklad 5.1: - metoda s parametrem

    class CStudent {
      // "globalni" promene tridy
      var $prijmeni, $jmeno;
      var $rocnik, $skupina, $kat_cislo;

      // metoda volana pri instancovani tridy -> predani parametru do globalnich parametru tridy
      function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) {
        $this->prijmeni = $prijmeni;
        $this->jmeno = $jmeno;
        $this->rocnik = $rocnik;
        $this->skupina = $skupina;
        $this->kat_cislo = $kat_cislo;
      } // END OF: function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) 

      // metoda pro vypis obsahu promenych
      function tisk ($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina;
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno;
	  	  } // END OF: viceradkovy vypis
      } // END OF: function tisk ($jeden_radek = true)
    } // END OF: class CStudent

Seznam a vysvětlení nových použitých chování a vlastností.

Příklad 5.2: - ukázka funkce konstruktoru

    $student1 = new CStudent("Cimrman", "Jára", 4, "AJ1", 13);
    $student1->Tisk(true); // vypse: Cimrman Jára ze 4 AJ1

    $student2 = new CStudent("Carda", "Retarda", 4, "AJ2", 13);
    $student2->Tisk(false); /* vypise: 4 AJ2
                                       Carda Retarda */

6. Řízení přistupu k metodám a proměnným

Věcí, která se by se nám nemusela líbit je, že vlastnost například $jmeno může změnit kdokoli odkudkoli a stačí mu k tomu jenom reference na objekt. Člověk by neměl věřit cizímu kódu a už vůbec ne svému, a tak budeme chtít přístupy ke $jmeno (ale i dalším atributům objektu) nějak ochránit. PHP od verze 5.0 zná celkem tři typy ochrany (proměnných, tedy atributů a také metod, tedy funkcí):

  1. public - veřejně přístupné; aneb žádná ochrana, výchozí stav
  2. protected - chráněné; k vlastnosti mají přístup vlastní instance a instance potomků třídy (něco o dědění bude dále v Kapitole 7.)
  3. private - jen a jen moje; k tomu se nedostane nikdo jiný než objekty dané třídy

Tato tři klíčová slova se při definici vlastnosti používají místo var, takže to může vypadat třeba takhle:
Příklad 6.1:

  protected $prijmeni, $jmeno; // nyni je $jmeno a $prijmeni pristupne pouze v telech metod tridy CStudent

Stejně jako s vlastnostmi a jejich ochranou je to i u metod. Akorát že tam klíčová slova značící přístup nenahrazují slovo function, nýbrž se umisťují před něj.
Příklad 6.2:

    public function __construct($$prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) { ... }
    public function tisk() { ... }

Doporučuji nikdy nepoužívat var (jak již bylo naznačeno, je to relikt z PHP 4.x; při definování vlastností tedy používat pouze klíčová slova řízení přístupu) a u funkcí vždy uvádět, jakou ochranu mají (bůhví, co si dokáží vývojáři PHP usmyslet do dalších verzí, třeba nakonec v PHP 6 bude výchozí private).


7. Skládání

Řekněme že jsme se rozhodli odlišit studenta maturanta od ostatních studentů. Existují dva způsoby jak delegovat (předávat) nějaké navržené (doporučené/funkční) schéma mezi objekty a to:

  1. Skládání objektů
  2. Specializace (dědění) objektů - Kapitola 8.

Příklad 7.1: - skládání a interface
Skládání objektů spočívá v tom, že jeden objekt osahuje referenci na jiný objekt (takže danému referencovanému objektu může zasílat zprávy). Takže budeme potřebovat novou metodu tisk, která zohlední našeho případného maturanta, nebo nematuranta. Přidání nové tiskové metody obnáší vytvořit novou třídu, která podporuje daný protokol. Takže si za tímto účelem vytvoříme interface třídy CTiskar.

    interface CTiskar {
      public function tisk($jeden_radek); // pozor parametrum metod nesmime prirazovat defaultni hodnoty!
                                          // ani zde nesmime defninovat clenske promenne
    } // END OF: interface CTiskar

Seznam a vysvětlení nových použitých chování a vlastností.

Třídy, které se zavazují k možnostem zasílat jim zprávy daného protokolu, rozhraní tzv. „implementují“:
Příklad 7.2: - implements

    class CStudujici implements CTiskar {
      // verejnte pristupne promene
      public $prijmeni, $jmeno;
      public $rocnik, $skupina, $kat_cislo;

      public function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) {
        // promene predany z interface tridy
        $this->prijmeni = $prijmeni;
        $this->jmeno = $jmeno;
        $this->rocnik = $rocnik;
        $this->skupina = $skupina;
        $this->kat_cislo = $kat_cislo;
      } // END OF: function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) 

      // metoda pro vypis obsahu promenych
      public function tisk ($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina;
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno;
	  	  } // END OF: viceradkovy vypis
      } // END OF: function tisk ($jeden_radek = true)
    } // END OF: class CStudujici implements CTiskar {

Seznam a vysvětlení nových použitých chování a vlastností.

Příklad 7.3: - vlastní skládání

    class CStudent {
      private $tiskar;
      public function __construct(CTiskar $tiskar) {
        $this->tiskar = $tiskar;
      } // END OF: public function __construct(CTiskar $tiskar)  

      // metoda pro vypis obsahu promenych
      function tiskni ($jeden_radek = true) {
        return $this->tiskar->tisk($jeden_radek);
      } // END OF: function tisk ($jeden_radek = true)
    } // END OF: class CStudent

Seznam a vysvětlení nových použitých chování a vlastností.

A jak můžeme potom snadno odvodit novou třídu CMaturant se stejnym interface jako CTiskar krásně vidíme na dalším příkladu.
Příklad 7.4: - jiný způsob implmentace metody tisk

    class CMaturant implements CTiskar {
      // verejnte pristupne promene
      public $prijmeni, $jmeno;
      public $rocnik, $skupina, $kat_cislo;

      public $odmaturoval; // true nebo false jestli odmaturoval;
      public $odmaturoval_arr = Array(true => 'Odmaturoval', false => 'Nedomaturoval');
      
      public function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) {
        // promene predany z interface tridy
        $this->prijmeni = $prijmeni;
        $this->jmeno = $jmeno;
        $this->rocnik = $rocnik;
        $this->skupina = $skupina;
        $this->kat_cislo = $kat_cislo;
        $this->odmaturoval = $this->_maturita();
      } // END OF: function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) 
      
      protected function _maturita() {
      // metoda nahodne rozhodne jestli student odmaturoval nebo ne a vrati to jako @boolean 
		    return (rand (1, 10) < 6);
      } // END OF: protected function _maturita() {
      
      // metoda pro vypis obsahu promenych
      public function tisk ($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina.
                     " [{$this->odmaturoval_arr[$this->odmaturoval]}]";
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno." [{$this->odmaturoval_arr[$this->odmaturoval]}]";
	  	  } // END OF: viceradkovy vypis
      } // END OF: function tisk ($jeden_radek = true)
    } // END OF: class CMaturant implements CTiskar

Seznam a vysvětlení nových použitých chování a vlastností.

A jak se to celé používá v praxi?
Příklad 7.5: - skládání v praxi

    $maturantik = new CMaturant("Cimrman", "Jára", 4, "AJ1", 13);
    $obycejny_student = new CStudujici("Petr", "Skočdostroje", 3, "AJ2", 15);
    
    $student1 = new CStudent($maturantik);
    $student1->tiskni(); // vytiskne nam Jaru Cimrmana s tim jestli odmaturoval nebo ne
    
    echo '<br />';
    $student2= new CStudent($obycejny_student);
    $student2->tiskni(false); // vytiskne nam Petra Skocdopole (obycejneho studenta) a jeste s odradkovanim.

8. Dědičnost

A nyní se již můžeme podívat i na dědičnost. Vytvoříme novou třídu, která zdědí některé atributy (proměnné) a metody (funkce) z výchozí třídy CStudnet v Příkladu 5.1. A neb již za sebou určitě máte Kapitolu 7., tak to vše zapíši v jednom příkladě a pod ním si rozebereme všechny nové syntaktické prvky a i některé navic:-).
Příklad 8.1: - dědičnost (extends)

    abstract class CStudent {
      // "globalni" promene tridy
      public $prijmeni, $jmeno;
      public $rocnik, $skupina, $kat_cislo;
      
      public function tiskni($jeden_radek = true){
        return $this->_tisk($jeden_radek);
      }
      
      abstract protected function _tisk($jeden_radek = true); // funkce tisk bude (musi byt!)
                                                              // implementovana v potomkovi
    } // END OF: abstract class CStudent 
    
    final class CMaturant extends CStudent {
      // verejnte pristupne promene
      public $odmaturoval; // true nebo false jestli odmaturoval;
      public $odmaturoval_arr = Array(true => 'Odmaturoval', false => 'Nedomaturoval');

      public function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) {
        // promene predany z interface tridy
        $this->prijmeni = $prijmeni;
        $this->jmeno = $jmeno;
        $this->rocnik = $rocnik;
        $this->skupina = $skupina;
        $this->kat_cislo = $kat_cislo;
        $this->odmaturoval = $this->_maturita();
      } // END OF: function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) 
	
      protected function _maturita() {
      // metoda nahodne rozhodne jestli student odmaturoval nebo ne a vrati to jako @boolean 
		    return (rand (1, 10) < 6);
      } // END OF: protected function _maturita() {
      // metoda pro vypis obsahu promenych

      protected function _tisk($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina.
                     " [{$this->odmaturoval_arr[$this->odmaturoval]}]";
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno." [{$this->odmaturoval_arr[$this->odmaturoval]}]";
	  	  } // END OF: viceradkovy vypis
      } // END OF: function _tisk ($jeden_radek = true)
    } // END OF: class CMaturant extends CStudent 

    class CStudujici extends CStudent {
      public function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) {
        // promene predany z interface tridy
        $this->prijmeni = $prijmeni;
        $this->jmeno = $jmeno;
        $this->rocnik = $rocnik;
        $this->skupina = $skupina;
        $this->kat_cislo = $kat_cislo;
      } // END OF: function __construct($prijmeni, $jmeno, $rocnik, $skupina, $kat_cislo) 
	
      protected function _tisk($jeden_radek = true) {
        if ($jeden_radek == true) {
	    		echo $this->prijmeni." ".$this->jmeno." ze ".$this->rocnik." ".$this->skupina;
	    	} // END OF: if ($jeden_radek == true) 
        else {
	  	  	echo $this->rocnik." ".$this->skupina."<br />\r\n";
	  	  	echo $this->prijmeni." ".$this->jmeno;
	  	  } // END OF: viceradkovy vypis
      } // END OF: function _tisk ($jeden_radek = true)
    } // END OF: class CStudujici extends CStudent 

Seznam a vysvětlení nových použitých chování a vlastností.

A jak třídy z Příkladu 8.1 použijeme vidíte na následujím příkladě.
Příklad 8.2: - dědičnost v praxi

    $student1 = new CStudujici("Cimrman", "Jára", 4, "H", 13);
    $student1->tiskni();
 
    echo '<br />'; // novy radek pro dalsi volani druhwe tridy 
    $student2 = new CMaturant("Carda", "Retarda", 4, "G", 13);
    $student2->tiskni(false); // odradkovany vypis

9. Úkol


10. Zdroje a citace

  1. ACHOUR, Mehdi, Friedhelm BETZ, Antony DOVGAL, Nuno LOPES, Hannes MAGNUSSON, Georg RICHTER, Damien SEGUY a Jakub VRÁNA. PHP: PHP Manual. [online]. [cit. 2012-08-21]. Dostupné z: www.php.net/manual/
  2. OOP v PHP. In: Programujeme.com [online]. [cit. 2012-08-24]. Dostupné z: http://programujte.com/clanek/2009113001-oop-v-php/
  3. VRÁNA, Jakub. Kniha 1001 tipů a triků pro PHP [online]. 1. vyd. 2010 [cit. 2012-08-21]. Dostupné z: http://php.vrana.cz/kniha-1001-tipu-a-triku-pro-php.php
  4. BURDA, Michal. PHP v objetí objektů. In: ROOT.CZ [online]. [cit. 2012-08-24]. Dostupné z: http://www.root.cz/clanky/php-v-objeti-objektu-1/
  5. ANTHARES. Object Oriented PHP Best Practices. In: Stackoverflow [online]. [cit. 2012-08-24]. Dostupné z: http://stackoverflow.com/questions/2407807/object-oriented-php-best-practices
  6. Poznámky neznámého učitele ze SPŠ Zlín :-)

HTML5 Powered with CSS3 / Styling, Device Access, Graphics, 3D & Effects, and Semantics
© Jan Wellech 2012-2016, ver. 1.1