PHP 5 OOP cheatsheet (tahák) | Ondřej Mirtes

PHP 5 OOP cheatsheet (tahák) | Ondřej Mirtes

ČTVRTEK
4. 3. 2010
PHP 5 OOP cheatsheet (tahák)

Zápis některých aspektů objektově orientovaného programování v PHP má poměrně velký WTF faktor a nejednou jsem narazil na pokročilého programátora, jak tápal, proč jeho kód způsobuje parse error či nějakou jinou chybu. Rozhodl jsem se proto oprášit můj blog a přehledně na jednom místě shrnout OOP syntax v PHP.

U jednotlivých popisů budu velice stručný, článek si neklade za cíl vysvětlit OOP, zaměřuje se pouze na způsob zápisu.

K hezkému objektovému kódu patří i jeho jednotná podoba, doporučuji k nastudování Nette Coding Standard.

Třídy

Definice nové třídy
//obecná třída
class A {

}

//abstraktní třída - nelze vytvořit instanci, to až u potomka
abstract class A {

}

//finální třída - nelze podědit
final class A {

}

//definice třídy B, která dědí od třídy A
class B extends A {

}

Konstruktor

Metoda, která se jmenuje __construct() a volá se při vytvoření instance nové třídy.

class A {
        public function __construct() {

        }
}

//konstruktor může mít parametry
class A {
        public function __construct($name) {

        }
}
Vytvoření instance
$a = new A; //pokud konstruktor nemá žádné povinné parametry
$a = new A(); //ekvivalentní zápis

$a = new A('foo'); //konstruktor s povinným parametrem

Rozhraní (interfaces)

//obecný interface
interface A {

}

//interface B, který dědí od interface A
interface B extends A {

}

//třída B, která implementuje interface A
class B implements A {

}

/* PHP umožňuje implementaci více rozhraní naráz */

//třída C, která implementuje interfaces A a B
class C implements A, B {

}

Viditelnost (zapouzdření)

U metod a statických/in­stančních členů (atributů třídy) lze nastavit viditelnost pro volání (u metod), resp. pro čtení a zápis (u atributů). PHP, stejně jako ostatní jazyky, podporuje tři typy viditelnosti:

  • public: volat metody a přistupovat k atributům může každý
  • protected: pouze samotná třída, její potomci (při překrývání – overriding – i předci) a sourozenci (dvě různé instance typu B či nadtypu A si navzájem mohou sahat na protected metody a atributy)
  • private: pouze samotná třída

Atributy

Definice atributů

Konvence říká, že všechny atributy by měly být definovány na začátku definice třídy.

class A {
        //instanční atributy
        var $a; //zastaralý zápis public
        public $b;
        protected $c;
        private $d;

        //statické atributy
        public static $e;

        //atributům lze předat výchozí hodnoty primitivních datových typů anebo pole
        //složitější data se musí předávat v konstruktoru
        private $f = '';
        private $g = 'foo';
        private $h = 108;
        private $i = array('bar');
}
Přistupování k atributům
$a = new A;

//zvenčí, k instančním
$a->a = 'foo';

//zvenčí, ke statickým
A::$e = 'bar';

//zevnitř třídy, k instančním
$this->c = 'foo';
echo $this->f;

//zevnitř třídy, ke statickým
self::$e = 'bar';

Konstanty

PHP umožňuje definovat neměnitelné atributy třídy, např. pro všelijaká nastavení a podporu principu DRY.

class A {

        //definice konstanty
        const DEFAULT_USER = 'foobar';
}

//přístup zvenčí
echo A::DEFAULT_USER;

//přístup zevnitř
echo self::DEFAULT_USER;

Metody

Definice metod
class A {

        //metody bez uvedení viditelnosti jsou brány jako public
        function methodFoo() {

        }

        public function methodBar() {

        }

        protected function methodFooBar() {

        }

        private function methodBarFoo() {

        }

        //metoda může být abstraktní, její definici pak necháváme na potomkovi třídy
        abstract public function methodAbstract();

        //metoda může být i finální, nelze ji pak v potomkovi překrýt
        final public function methodFinal() {

        }

}
Parametry metod
class A {

        //metoda, která přijímá dva parametry
        public function methodExample($a, $b) {

        }

        //PHP neumožňuje přetěžování metod (overloading), umí ale nepovinné parametry
        //metodu lze zavolat se dvěma nebo třemi parametry
        public function methodFoo($name, $age, $sex='male') {

        }

        //lze definovat, jakého typu má přijímaný parametr být
        //nejde použít pro primitivní datové typy - lze vynutit pouze pole, interface nebo třídu - funguje zde polymorfismus
        public function methodBar(array $names, Person $p, Traversable $t) {

        }

        //i metody mohou být statické
        public static function methodStatic() {

        }
}
Volání metod
$a = new A;

//instanční zvenčí
$a->methodExample('foo', 'bar');

//instanční zevnitř
$this->methodFoo('foo', 'bar');
$this->methodFoo('foo', 'bar', 'female');

//statické zvenčí
A::methodStatic();

//statické zevnitř
self::methodStatic();

Překrývání

Pokud v potomkovi definuji atribut nebo metodu s názvem, který existuje už v předkovi, a jejich viditelnost je public či protected, dojde k překrytí (overriding).

class A {
        public $foo = 'foo';

        public function methodExample() {
                return 'foo';
        }
}

class B extends A {
        public $foo = 'bar';

        public function methodExample() {
                return 'bar';

                //pomocí parent::methodExample() mohu zavolat metodu předka
        }
}

$b = new B;
echo $b->foo; //vypíše bar
echo $b->methodExample(); //vypíše bar

Pokud se v třídním předkovi odkazuji na public/protected atribut či metodu, která je v potomkovi přepsána, volá se/přistupuje se k implementaci potomka.

Pozor! Tento způsob nefunguje u statických atributů a metod, tam se při přístupu pomocí self:: bude stále volat implementace v předkovi. Tento nedostatek řeší late static binding v PHP 5.3, viz níže.

class A {
        protected $foo = 'foo';

        public function getFoo() {
                return $this->foo;
        }
}

class B extends A {
        protected $foo = 'bar';
}

$b = new B;
echo $b->getFoo(); //vypíše bar

Špatné návyky

Zdrojové kódy v této části doma, v práci, ani ve škole nezkoušejte! ;)

PHP kvůli své skriptovací povaze umožňuje v OOP opravdu nehezké věci.

$a = 'foo';
$b = 'bar';

//podmíněné dědění? 11 z 10 programátorů pláče
if ($a == $b) {
        class B {

        }
} else {
        class B extends A {

        }
}
//pokud chci pracovat s nějakým atributem, musí být ve třídě vždy definovaný
class A {
        //fuj!
        if ('foo' == 'bar') {
                public $aaa;
        }

        public function fooBar() {
                if (isset($this->aaa)) { //nesmyslná kontrola, ve správném kódu musí $this->aaa vždy existovat
                        //...
                }
        }
}

Dynamické názvy

Pokud k tomu máme důvod, můžeme na základě nějaké proměnné či výpočtu rozhodovat, jaká třída se použije, k jakému atributu se přistoupí a jaká metoda se zavolá.

Musíme být samozřejmě připraveni na všechny hodnoty, kterých daná proměnná může nabýt.

$den = date('D'); //nabývá hodnot Mon až Sun

$a = new $den; //zavolá se třída s názvem dne v týdnu

//od PHP 5.3 lze přistupovat i ke statickým atributům/konstantám/metodám
$den::TRIDNI_KONSTANTA;
$den::methodStatic();

//volání metody
$method = 'mojeMetoda';

//zavolá se instanční metoda mojeMetoda() třídy Thu (pokud je čtvrtek ;))
$a->$method();

//pokud chci zapsat složitější logiku, slouží k tomu složené závorky
$bool = true;

//zavolá se metoda allow() nebo deny()
$a->{$bool ? 'allow' : 'deny'}();

Jmenné prostory (namespaces) (PHP 5.3)

Pokud se vám hodí mít dvě třídy stejného názvu, tak je stačí dát do dvou různých jmenných prostorů a nemáte problém. Ale pozor! Namespaces spíše přidělávají práci, než aby ji ulehčovali. Přesto se dá přijít na způsob, jak práci s namespaces minimalizovat na nejmenší možnou mez.

//lze mít více úrovní jmenných prostorů, \ je oddělovač
namespace Foo\Bar;

//v jiných souborech je nyní ke třídě A nutné přistupovat jako k Foo\Bar\A
class A {

}
namespace MujNamespace;

class B {

        public function methodTest() {
                /**
                 * pokud chci uvnitř nějakého namespace přistoupit k úplně jinému namespace,
                 * musím zvolit absolutní "cestu" s \ na začátku
                 */
                $a = new \Foo\Bar\A;

                /**
                 * jinak by došlo k tomuto:
                 */
                $a = new A; //třída MujNamespace\A neexistuje!

                /**
                 * Pokud na začátku souboru pod definicí namespace zavolám:
                 * use \Foo\Bar\A;
                 * ...tak už se na třídu mohu odvolávat klasicky:
                 */
                $a = new A; //funguje!
        }

}

Late static binding (PHP 5.3)

Problém s překrýváním statických atributů a metod jsem zmínil už výše, zde uvedu jen praktický příklad:

class A {
        protected static $foo = 'foo';

        public function methodLsb() {
                echo self::$foo . "\n";
                echo static::$foo . "\n";
        }
}

class B extends A {
        protected static $foo = 'bar';
}

$b = new B;
$b->methodLsb();

//vypíše:
foo
bar
Ondřej Mirtes

Komentáře RSS

Přidat nový komentář
 

[1] Honza Marek | 4. 3. 2010 23:26

Radvisovi to nepomůže.

[2] Ondřej Brejla | 5. 3. 2010 8:59

Ale je to hezké.

[3] v6ak | 5. 3. 2010 13:44

„Pozor! Tento způsob nefunguje u statických atributů a metod, tam se při přístupu pomocí self:: bude stále volat implementace v předkovi. Tento nedostatek řeší late static binding v PHP 5.3, viz níže.“ Já bych to za nedostatek nepovažoval. Naopak LSB mi trošku přijde jako hackování logiky.

Jinak o těch „worst practices“ jsem nevěděl.

K dynamickému přístupu by se hodilo připsat ještě zmínku o Refection API.

[4] Ondřej Mirtes | 5. 3. 2010 15:32

[3]: LSB nemusíš používat, pokud nechceš, self:: funguje stále stejně. A static:: si oblíbili tvůrci ORM, protože pomocí toho jde napsat hezčí API.

Podle mě by se statické metody měly chovat stejně jako ty instanční, že se nepřekryjí je přinejmenším divné.

A vůbec, při psaní toho článku jsem si uvědomil, jaké to OOP dokáže být zmatek a čeho všeho se to vlastně týká :)

Přemýšlím nad druhým dílem, kde bych se podíval na polymorfismus, magické metody, všelijaké funkce a na co si ještě vzpomenu. Na Reflection může taky přijít řeč :)

[5] blizzy | 5. 3. 2010 21:22

tohle by chtělo zpracovat do pdf a vytisknout :)

[6] rs@income.cz | 22. 3. 2010 5:26

Hele ale tohle je bomba! To by měly logicky přetisknout všechny spřátelené listy :))

Už si to dávám na plochu :))

Díky!

R

[7] David Vlček | 18. 6. 2010 10:17

Díky moc za tento stručný a velmi užitečný přehled syntaxe. Ukládám si s vědomím, že není důležité všechno vědět, ale vědět kde to hledat ;o)

[8] charlie | 15. 7. 2010 8:07

//PHP neumožňuje přetěžování metod (overloading)

Je tohle pravda? K čemu jsou potom např.: metody __get; __set; __call ?

[9] srigi | 15. 7. 2010 11:27

Hi, pekny blogpost. Mohol by si na zaciatok sekcie „Špatné návyky“ pridat nejake IDcko do HTML kodu, aby slo na nu priamo napisat URL. THX.

[10] Ondřej Mirtes | 16. 7. 2010 13:27

[8]: Tyhle magické metody se volají, pokud uživatel objektu přistupuje k nedeklarovaným metodám a vlastnostem. Jako „method overloading“ je v normálních jazycích chápáno něco jiného – více metod pod stejným názvem, které se liší typem či počtem parametrů (http://en.wikipedia.org/…_overloading).

[11] Ondřej Mirtes | 16. 7. 2010 13:31

[9]: Nastavil jsem Texy, aby generovalo IDčka, nevěděl jsem, že to je defaultně vypnuté ;)

[12] Charlie | 19. 7. 2010 13:09

sice nevím, jak v jiných jazycích, ale method overloading v PHP je známý pojem: http://php.net/…rloading.php

[13] Jakub Vrána | 9. 11. 2010 17:50

if ($x) { public $aaa } způsobí syntax error.

Vlastnost by sice měla existovat, ale může mít hodnotu null, takže isset($this->aaa) může mít smysl.

Info

Ondřej Mirtes

23 let

student ČVUT FEL
& webový vývojář

počítače, hry, DJing

Foto

Včera: @jantichy I've met some gentle people there…

Včera: Vždy, když mi volá blokované číslo, čekám na druhé straně Sadako Yamamurovou, která mi řekne, že do týdne zemřu.

Včera: Po shlédnutí Půlnoci v Paříži jsem tomu městu odpustil i vykradené auto. Chci se tam co nejdřív vrátit!

Včera: I have a feeling we're not in Kansas anymore. http://t.co/7BB51eKC

Před 9 dny: January 28th 2012 http://t.co/LsYUNvNm

Anketa

Rubriky

Top 5 za únor 2009

  • 1. The Doppler Effect – Beauty Hides In The Deep (The Blizzard Remix)
  • 2. Nifra – Complicated Life
  • 3. Will Holland – Tears In The Rain
  • 4. Myon & Shane 54 ft. Carrie Skipper – Vampire
  • 5. Rank1 – L.E.D. There Be Light

Winamp mi právě hraje

  • 112d Thomas Coastline & CrossRyders – Global Electronic Music Sessions 044 on AH.FM
  • 112d Next on Air: Eunostos – Club Nights 032 on AH.FM
  • 112d Thomas Coastline & CrossRyders – Global Electronic Music Sessions 044 on AH.FM
  • 112d Sean Tyas – Tytanium Sessions 116 on AH.FM
  • 112d Ferry Tayle – Flightbook Bratislava Edition on AH.FM
* * * * * * * * * * * * *