Ondřej Mirtes

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

‹ Testování není nástroj, ale metoda vývoje Dokonalý kontaktní formulář za 10 minut ›