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/instanč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