Wraz z pojawieniem się PHP w wersji 5.3, światło dzienne ujrzała nowa właściwość języka o bardzo groźnie brzmiącej nazwie Late Static Binding. Na czym owa własność polega? Jest ona ściśle związana z metodami statycznymi oraz widocznością / kontekstem poszczególnych metod w klasach.
Żeby lepiej pokazać po co w ogóle dodano do języka statyczne wiązanie, warto zobaczyć jakie limity nakłada na nas słowo kluczowe self – bo dotychczas tylko ono pozwalało statycznie wywołać metodę z klasy (oczywiście jeszcze jest parent w przypadku kiedy mówimy o metodach rodzica).
class A
{
public static function who()
{
echo __CLASS__; // 3
}
public static function test()
{
self::who(); // 2
}
}
class B extends A
{
public static function who()
{
echo __CLASS__;
}
}
B::test(); // 1
Powyższy kod wyświetli na ekranie „A”.
Prześledźmy w takim razie kod. Cały „przepływ” zaczyna się oczywiście od wywołania metody B::test(); w pierwszym kroku. Metoda ta nie robi nic poza wywołaniem kolejnej metody o nazwie who(). I tutaj zaczynają się „schody”. Jako że łańcuch wywołań owych metod zaczynamy z klasy B sugeruje to, że zależy nam na wykonaniu metody who() również z klasy B. self jednak działa inaczej, a mianowicie, działa ono w kontekście klasy, w której dana metoda została zdefiniowana. Dlatego też egzekucja takiego kodu self::who(); z klasy A, to mniej więcej tak jak napisanie A::who().
I tutaj z pomocą przychodzi „nowe stare” słowo static. Stare dlatego, że w PHP już przecież z takim słowem nieraz się na pewno spotkałeś, a nowe bo może być teraz użyte w nieco innym kontekście niż dotychczas (oczywiście jego dawne użycie nadal jest aktualne!). Zmodyfikujmy powyższy kod dodając jedną linijkę do metody A::who():
class A
{
public static function who()
{
echo __CLASS__; // 2.2
}
public static function test()
{
self::who(); // 2.1
static::who(); // 3.1
}
}
class B extends A
{
public static function who()
{
echo __CLASS__; // 3.2
}
}
B::test(); // 1
Powyższy kod wypisze na ekranie: „AB”.
Kroki drugi i trzeci zostały podzielone na dwa etapy w celu polepszenia czytelności przykładu. Z kroku 2.1 idziemy do 2.2 czyli do metody A::who() – jak widać, do tej pory wszystko jest tak samo jak w pierwszym przykładzie, stąd też na ekran zostaje wypisane „A”.
Następnie w kroku trzecim program wykonuje metodę who() ale „wyskakuje” z klasy w której aktualnie się znajduje i „idzie” do klasy B – do tej, która zaczęła cały łańcuch wywołań. I tym właśnie różni się wywołanie static od self.
static odwołuje się do klasy, z której nastąpiło początkowe wywołanie – czyli krok 3.1 w momencie wykonywania skryptu „wygląda” dla interpretera tak: B::who().
Pułapki z widocznością
Late static binding to naprawdę fajny wynalazek, ale jeśli nie masz doświadczenia w jego użyciu musisz mieć się na baczności – zwłaszcza jeśli chodzi o zmienne prywatne. Popatrz na poniższy przykład:
class A
{
private static function who()
{
echo __CLASS__;
}
public static function test()
{
self::who(); // 1.1, 2.1
static::who(); // 1.2, 2.2
}
}
class B extends A
{
}
class C extends A
{
private static function who()
{
echo __CLASS__;
}
}
B::test(); // 1
C::test(); // 2
Ten przykład wyświetli na ekranie „AAA” a następnie błąd „Fatal error: Call to private method C::who() from context ‚A’”.
Jeśli uważnie przeanalizowałeś poprzednie przykłady powinieneś się przynajmniej domyślać dlaczego na koniec dostajemy błąd.
Najpierw do słowa dochodzi klasa B i tutaj nic nadzwyczajnego się nie dzieje: krok 1.1 to wywołanie metody A::who, natomiast w kroku 1.2 odpalamy metodę B::who (pamiętając, że nadal jesteśmy w kontekście A) – ale jako że metoda ta nie jest zdefiniowana w klasie B interpreter „szuka” jej w klasie A.
W przypadku klasy C sytuacja jest już nieco inna, jako że klasa ta definiuje własną metodę prywatną o nazwie who. Dlatego w kroku 2.1 wszystko jest jeszcze w porządku, jako że self::who() odwołuje się do metody, co prawda również prywatnej, ale z tego samego kontekstu z którego następuje wywołanie – w skrócie mówiąc: self::who() wywołane z A wykonuje rzecz jasna metodę A::who(). Problem natomiast pojawia się przy wywołaniu static::who() gdyż tutaj z kontekstu A chcemy wywołać prywatną metodę innej klasy – co oczywiście jest zabronione.
Jeszcze uwaga dla ciekawych świata – zamień private na protected w klasie C i zobacz co się stanie. I oczywiście zastanów się dlaczego