Jestem dopiero początkującym programistą, ale zajmuje się hobbystycznie programowaniem aplikacji internetowych od kilku lat. Moja wiedza na temat składni języka PHP nie jest mała, więc wydawało mi się, że nic nowego, a tym bardziej spektakularnego nie może się w niej znajdować... takie przeświadczenie miałem do dzisiaj. Przeglądając ciekawy kurs dotyczący tegoż języka natknąłem się na ciekawą składnię, która okazała się dla mnie całkowicie niezrozumiała:

class Controller { ... public function executeAction() { return $this->{$this->action}(); } }

Chodzi oczywiście o wynik zwracany w powyższej metodzie. Przez kilkanaście minut nie mogłem dojść do tego, co tu się dzieje. Na szczęście moja umiejętność googlowania stoi na dość zaawansowanym poziomie i udało mi się znaleźć artykuł odpowiadający na moje pytania i to z nawiązką!

Wywoływanie funkcji ze zmiennej

O co tu właściwie chodzi? W języku PHP istnieje składnia umożliwiająca wywoływanie funkcji dynamicznie, korzystając ze zmiennych. Funkcje muszą być oczywiście wcześniej zdefiniowane ale metoda, która ma zostać wywołana, może zostać określona już podczas działania skryptu. Jednak to nie wszystko co można powiedzieć na ten temat, ponieważ ta właściwość języka została rozbudowana do takiego poziomu, że głowa mała. Spójrz proszę na poniższy przykład:

function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } echo giveMeFiveNumbersWithSpaces();

Wynikiem takiego skryptu będzie oczywiście:

1 2 3 4 5

Można zmusić interpreter języka PHP, aby wykonał funkcję pobierając jej nazwę ze zmiennej. Przykładowo:

function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } $functionName = "giveMeFiveNumbersWithSpaces"; echo $functionName();

Bardzo ciekawa koncepcja! Można dzięki temu wybrać dynamicznie funkcję do wykonania w zależności, co znajduje się w przechowywanej zmiennej. Jednak jeżeli to byłby koniec nowości, to nie wywarło by na mnie tak dużego wrażenia i nie napisałbym o tym na blogu. ;) Składnia pozwala na różne scenariusze jej wykorzystania. Pierwszy z tych intuicyjnych: jeżeli można wykorzystać zmienne, to można także wykorzystać literały (tylko w jakim celu):

function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } echo "giveMeFiveNumbersWithSpaces"();

Kolejny scenariusz, który spowodował u mnie lekką euforię: można także zmienne te pakować do tablic! I tak:

function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } $functionName = ["giveMeFiveNumbersWithSpaces", "giveMeFiveLettersWithSpaces"]; echo $functionName[1]() . $functionName[0]() . $functionName[1]();

Skrypt ten daje wynik na wyjściu:

a b c d e 1 2 3 4 5 a b c d e

W sumie jak najbardziej logiczne. Ale to jeszcze nie koniec! Wiedząc jak działają zmienne w PHP można było się domyśleć, jak "upakować funkcje w zmienne". Jest jeszcze co najmniej jeden ciekawy przypadek związany z tablicami i funkcjami ze zmiennych: można wywoływać bezpośrednio metody z klas!

Wywoływanie metody ze zmiennej

Najpierw spróbujmy wywołać funkcje znajdującą się w środku klasy bez żadnego operatora:

class CharacterReturner { public function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } public function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } $functionName = "giveMeFiveNumbersWithSpaces"; echo $functionName();

Niestety (albo na szczęscie), tak skonstruowany skrypt nie wykona się:

Fatal error: Uncaught Error: Call to undefined function giveMeFiveNumbersWithSpaces() …

Nic dziwnego, w końcu metody klasy nie są widoczne globalnie, a sama klasa nie została zainicjowana chociażby raz. Spróbujmy podejść do problemu trochę inaczej: zainicjować obiekt i podmienić żądaną metodę:

class CharacterReturner { public function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } public function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } $functionName = "giveMeFiveNumbersWithSpaces"; $object = new CharacterReturner; echo $object->$functionName();

No i działa. :) Uśmiech ponownie zagościł na mojej twarzy. Dochodzimy właśnie do momentu, w którym jestem w stanie zrozumieć fragment kodu z początku tego wpisu. Jeżeli użyjemy metody z wnętrza klasy, czyli innej metody:

class CharacterReturner { private $functionName; public function __construct() { $this->functionName = "giveMeFiveLettersWithSpaces"; echo $this->{$this->functionName}(); } public function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } new CharacterReturner();

No i wspaniale! Zagadka została rozwiązana. Ale jeżeli jednak myślisz, że to już koniec to jesteś w grubym błędzie! Znalazłem jeszcze jedną sztuczkę składniową dotyczącą tablic:

class CharacterReturner { public function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } public function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } $functionName = ["CharacterReturner", "giveMeFiveNumbersWithSpaces"]; echo $functionName();

Taki fragment zwraca… nie, nie błąd, jak można by się tego spodziewać! Gdy nie podamy indeksu tablicy, z którego ma zostać pobrana nazwa funkcji, to wykonanie skryptu da wynik:

1 2 3 4 5

Chwila - co się tu wyprawia?! Została wywołana funkcja z niezainicjowanej klasy! Nic nie zmienia fakt, że ta metoda posiada modyfikator public. Ani klasa, ani jej wnętrze nie jest statyczne. A po drugie, dlaczego została wywołana funkcja z drugiego miejsca tablicy? Okazuje się jednak, że jeżeli mamy tablicę i nie podamy zakresu, z którego ma zostać pobrana nazwa funkcji to zostaje ona pobrana w następujący sposób: z pierwszego miejsca tablicy skrypt pobiera nazwę klasy, a z następnego nazwę funkcji do wywołania… Bardzo zabawna sprawa.

Zmieńmy widoczność funkcji na private i zobaczmy co się stanie:

class CharacterReturner { private function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } $functionName = ["CharacterReturner", "giveMeFiveNumbersWithSpaces"]; echo $functionName();

Wynik działania programu:

Fatal error: Uncaught Error: Call to private method CharacterReturner::giveMeFiveNumbersWithSpaces() …

Fatal error prawdę ci powie! Metody wywoływane tym magicznym sposobem stają się niedostępne.

Na koniec kilka słów na temat wsparcia w najnowszej wersji języka. Powyższa składnia jest jak najbardziej wspierana w najnowszej wersji PHP 7, została nawet dodana możliwość wywoływania z literału metody statycznej z operatorem zakresu:

class CharacterReturner { public static function giveMeFiveNumbersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= $i . " "; } return $string; } public function giveMeFiveLettersWithSpaces() { $string = ""; for($i = 1; $i <= 5; $i++) { $string .= chr($i + 96) . " "; } return $string; } } $functionName = "CharacterReturner::giveMeFiveLettersWithSpaces"; echo $functionName();

To już koniec mojego krótkiego wywodu. Więcej informacji na temat tej funkcjonalności znajdziesz pod adresem, z którego czerpałem informacje: PHP: Variable functions - Manual. A co ty uważasz na temat tej funkcjonalności? Czy podzielasz mój entuzjazm? Korzystasz z niej na co dzień? Zachęcam do komentowania.