This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Języki skryptowe, ze względu na swój charakter [kod, to tylko zwykły tekst, który zaczyna “coś” znaczyć dopiero w momencie interpretacji] mają praktycznie nieograniczone możliwości manipulacji stanem aktualnie przetwarzanego żądania, ze względu na to, że w dowolnym momencie możemy dołożyć żądany kawałek kodu poprzez funkcje typu require_once(). PHP jest jednym z takich języków, stąd nieobce są mu pewne “dziwne” na pierwszy rzut oka funkcjonalności. Być może zastanawiasz się, Czytelniku, czym są wspomniane w tytule “zmienne zmienne” - po lekturze tego artykułu na pewno nie będziesz zawiedziony informacjami wyniesionymi z niniejszego wpisu, więc serdecznie zapraszam do kliknięcia w link “Czytaj dalej”. ;]

Wstęp.

Programując w językach kompilowanych na pewno zauważyłeś, że po przetworzeniu kodu przez kompilator mamy niewielkie możliwości manipulacji strukturą kodu. Mam tu na uwadze szczególnie język C++, w którym raz zapisane funkcje, zmienne, klasy, istnieją przez cały czas działania programu. Dodawanie nowych bytów tego typu jest praktycznie niemożliwe w tradycyjny sposób. Jedyne, co oferują współczesne biblioteki to tzw. refleksja, czyli możliwość analizy struktury kodu i wyciągania informacji o poszczególnych jego elementach.

W językach tych zabroniona jest wszelka “dynamika” struktury kodu, czyli dynamiczny wybór klas, funkcji, zmiennych realizujących określone zadania. Oczywiście to wszystko jest możliwe na pewnym zdefiniowanym przez programistę poziomie abstrakcji, ale ja mam na myśli raczej możliwości oferowane przez sam język. Żeby zilustrować “problem”, zamieszczam poniżej kilka fragmentów kodu w języku PHP, “niemożliwych” w C++:

Dynamiczny wybór klasy przy tworzeniu obiektu:

1
2
3
4
5
6
$name = 'SomeClass';
if('other' == $module)
    {
    $name = 'OtherClass';
    }
$object = new $name();

Dynamiczny wybór metody do wykonania:

1
2
3
4
5
6
$name = 'someMethod';
if('other' == $module)
    {
    $name = 'otherMethod';
    }
call_user_func($name, array());

Dynamiczny wybór użytej zmiennej:

1
2
3
4
5
6
$name = 'someVariable';
if('other' == $module)
    {
    $name = 'otherVariable';
    }
$value = ${$name};

Zdradziłem w tych listingach nieco informacji o temacie wpisu zanim je dobrze wyjaśniłem, ale mam nadzieję, że jeśli przeczytasz je ze zrozumieniem, to potem będzie prościej zrozumieć samo wyjaśnienie. Na pewno pojawi się jeszcze jeden wpis o podobnym mechanizmie, ale na razie nie będę zdradzał o co chodzi - Ci, którzy wiedzą i tak się domyślą. ;]

Zmienne zmienne.

Czym są te tajemnicze "variable variables", przetłumaczone na język polski jako "zmienne zmienne"? Otóż, jest to jeden z ciekawszych mechanizmów PHP pozwalający na dynamiczny wybór zmiennej używanej przy danej operacji [listing #3]. Tradycyjnie polecam lekturę manuala, który bardzo przystępnie tłumaczy "w czym rzecz".

Cały pomysł polega na tym, że w zakresie zmiennych naszego skryptu możemy wznieść się o jeden poziom abstrakcji wyżej i decydować o tym, która z nich [podając jej nazwę jako inną zmienną] zostanie faktycznie odczytana z pamięci i umieszczona w danej linijce kodu. Brzmi to strasznie niejasno, więc niech kod mówi sam za siebie. ;]

Rozważmy następujące deklaracje zmiennych:

1
2
$firstVariable = 'first value';
$secondVariable = 'second value';

W tym momencie, żeby zdecydować, która z nich  zostanie użyta, zazwyczaj napisalibyśmy sobie warunek:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$variableValue = '';
if(true == $conditional)
    {
    $variableValue = $firstValue;
    }
else
    {
    $variableValue = $secondValue;
    }

// or

$variableValue = (true == $conditional)
    ? $firstValue
    : $secondValue;

echo $variableValue

Można to jednak o wiele bardziej uprościć, korzystając właśnie ze zmiennych zmiennych. Możliwe są dwie konstrukcje, pierwsza z dwoma dolarami w identyfikatorze zmiennej:

1
2
3
4
5
$variableName = (true == $conditional)
    ? 'firstValue'
    : 'secondValue';
$variableValue = $$variableName;
echo $variableValue;

Warto w tym momencie wspomnieć, że tych “dolarów” może być więcej niż dwa, wtedy każdy kolejny poziom zagnieżdżenia wskazuje na kolejną zmienną:

1
2
3
4
5
6
$first = 'second';
$second = 'third';
$third = 'fourth';
$fourth = 'target value';
$value = $$$$first;
echo $value; // "target value"

Druga konstrukcja z nawiasami klamrowymi:

1
2
3
4
5
$variableName = (true == $conditional)
    ? 'firstValue'
    : 'secondValue';
$variableValue = ${$variableName};
echo $variableValue;

Możemy ją także zagnieździć:

1
2
3
4
5
6
$first = 'second';
$second = 'third';
$third = 'fourth';
$fourth = 'target value';
$value = ${${${$first}}};
echo $value; // "target value"

Jak widać, w kodzie warunkowym podstawiamy pod zmienną $variableName tylko nazwę zmiennej, której chcemy użyć, “rozwiązanie” tej nazwy i odczytanie właściwego identyfikatora możemy swobodnie powierzyć interpreterowi PHP.

Podsumowanie.

Mam nadzieję, że udało mi się przedstawić ideę zmiennych zmiennych w miarę czytelny i prosty sposób. Jest ona relatywnie prosta, ale dla osób, które nie miały wcześniej styczności z bardziej zaawansowanym programowaniem w PHP, ew. w ogóle z tak szeroko pojętą "dynamiką" struktury kodu może to stanowić pewien problem. W każdym razie zawsze służę pomocą w wyjaśnieniu wszelkich zagadek / niejasności w komentarzach. Dziękuję za lekturę wpisu i zapraszam w niedzielę na kolejną edycję Linkdumpa. ;]