Dzisiejszy wpis jest sponsorowany przez projekt, którego tworzenie obiecałem sobie w momencie, kiedy będę miał gotową całą pracę inżynierską. Ze względu na fakt, że do zrobienia zostały już tylko ostatnie poprawki i przygotowanie do samej obrony, stwierdziłem, że mogę powoli zacząć przygotowywać sobie środowisko pracy. Początki, jak to początki - sprawiają najwięcej problemów, stąd też kolejny wpis problemowy.

Problem: Odziedziczone metody klasy bazowej wykonują się w zakresie klasy bazowej.

Problem jest teoretycznie prosty - mamy dany kod:

class Base
	{
	protected function example()
		{
		echo __CLASS__;
		}
	}

class Derived extends Base
	{
	public function sample()
		{
		$this->example();
		}
	}

$derived = new Derived();
$derived->sample();

Jak myślicie, co pokaże wywołana metoda sample()? Nie, moi drodzy, nie będzie to “Derived”. Dziedziczenie w PHP jest naprawdę mocno zakręcone, dlatego musicie wiedzieć, że odziedziczona metoda klasy bazowej wykonuje się w zakresie [ang. scope] klasy… bazowej.

Co to oznacza? Ni mniej, ni więcej niż to, że wszelkiego rodzaju operacje odnoszące się do nazwy oraz położenia klas pochodnych będą w najlepszym przypadku nietrywialne. Moim problemem było pobieranie ścieżki do katalogu, w którym znajdowała się klasa pochodna. Jak to zrobić?

Rozwiązanie: Wykorzystanie refleksji - klasa ReflectionClass.

Stara zasada programistów brzmi: “jeśli nie da się drzwiami, wejdź oknem”. Może nie do końca ma to związek z programowaniem, ale tak mniej więcej można określić wszystkie techniki pozwalające na zmuszenie bezmyślnego narzędzia do działania dostosowanego do wymagań programisty.

Od dłuższego czasu w programowaniu obecne jest pojęcie refleksji. Język programowania może zostać nazwany refleksywnym, jeśli w trakcie wykonywania programu / interpretowania skryptu jest w stanie m. in. pobierać dane na temat samej struktury kodu. Język PHP nie jest pod tym względem gorszy i także zapewnia rozszerzenie Reflection, w którym znajduje się kilka klas pozwalających na wykonywanie operacji na różnych elementach języka.

Ze względu na to, że chciałem znaleźć informacje na temat danych klasy, musiałem użyć klasy ReflectionClass. Do rozwiązania problemu potrzebna też była informacja o tym, że pomimo wykonywania kodu w zakresie klasy bazowej, zmienna $this cały czas odnosi się do zakresu klasy pochodnej. Rozważmy zatem następujący kod pliku klasy bazowej [/www/base/base.php]:

class Base
	{
	protected function example()
		{
		echo dirname(__FILE__).'<br />';
		$reflector = new ReflectionClass(get_class($this));
		echo dirname($reflector->getFileName());
		}
	}

i kod klasy pochodnej, wraz z wywołaniem [/www/derived/derived.php]:

require_once('../base/base.php');

class Derived extends Base
	{
	public function sample()
		{
		$this->example();
		}
	}

$derived = new Derived();
$derived->sample();

Wynik:

/www/base
/www/derived

Jak widać, refleksja pomogła tam, gdzie PHP nie chciał współpracować. ;] Na pewno wiele osób pomyśli sobie w tym momencie - “a wydajność?”. Otóż wydajność jest taką sprawą, o której się myśli dopiero, jak wszystko działa. Po drugie narzut czasowy wykorzystania refleksji nie jest wcale duży, o ile używa się jej z rozwagą. Jedno-dwukrotne odwołanie się do niej w trakcie działania skryptu nie powinno przekraczać 1-2ms narzutu w stosunku do “oryginalnego” kodu. Za to zysk płynący z rozwiązania problemu w czytelny i poprawny programistycznie sposób jest nie do przecenienia.

Dziękuję za uwagę i zapraszam już w niedzielę na kolejny Linkdump. Oczywiście formularz komentarzy cały czas czeka na Wasze opinie - zachęcam do dzielenia się swoimi spostrzeżeniami.