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

Od pewnego czasu korzystam z pewnej bardzo ciekawej aplikacji pomagającego w zarządzaniu projektami. Redmine, bo tak jej “na imię” spełnia praktycznie wszystkie moje wymagania w tym zakresie, poza faktem, że jej wydajność jest “mocno średnia” [jest napisana w Ruby on Rails]. Dzisiaj jednak siadając przy komputerze stwierdziłem, że spróbuję czegoś nowego. Ze względu na to, że miałem przez chwilę styczność z polecanym przez jednego z kolegów OpenGoo zaprosiłem “na warsztat” właśnie ten “kawałek kodu”.

Wstęp.

Instalacja OpenGoo przebiegła nadzwyczaj spokojnie, po uprzednim stworzeniu przeznaczonej nań bazy danych wystarczyło kliknąć dwa razy “dalej” i… już. Potem rejestracja konta administratora i szybkie przekierowanie na stronę główną aplikacji. Wszystko byłoby “cacy”, gdyby nie jeden mankament - już w oknie rejestrowania konta administratora zauważyłem, że PHP wyrzuciło na ekran “jakiś błąd”:

Deprecated: Call-time pass-by-reference has been deprecated in [file] on line [line]
Myślę sobie - działa, to po co ruszać [zainstalowałem najnowszą w tym momencie wersję 1.7 RC2, więc na pewno jakieś błędy jeszcze są]. Zaraz jednak zreflektowałem się i pomyślałem "jak to, ja nie dam rady? NO WAI!". Tym sposobem pomysł na dzisiejszy wpis sponsoruje instalacja OpenGoo. ;]

Nazwanie tego problemu błędem jest trochę niepoprawne, ponieważ to, co PHP w swej łaskawości wydrukował na ekranie jest tylko ostrzeżeniem [warning, w error_reporting() jest to flaga E_WARNING], co nie zmienia faktu, że ostrzeżeniem o dosyć denerwujących skutkach. Jak się później okazało przez ten “błąd” niemożliwe było załadowanie pliku języka, stąd wszystkie napisy wyglądały w następujący sposób:

Cannot load language file: [plik].
Trzeba było więc podjąć bardziej radykalne kroki niż "działa, można zostawić". ;]

Na pierwszy rzut oka problem jest co najmniej nie zrozumiały, jednak każdy kto miał trochę więcej do czynienia z programowaniem wie, że istnieje takie coś jak “referencja”, a nawet co to jest. ;] Także tłumacząc z polskiego na nasze “przekazanie referencji podczas wywołania zostało zdeprecjonowane w [pliku] na [linii]”. ;] Teraz już jest trochę jaśniej, ale nadal nie wiemy co powoduje ten błąd.

Problem.

Otóż: w języku PHP to funkcja decyduje o "poziomie indyrekcji" przekazanych parametrów [ang. indirection level - uwielbiam spolszczenie tego terminu ;]], stąd nie możemy wywołać funkcji z argumentem będącym referencją. Brzmi "ciężko"? Spokojnie, już daję przykład. Weźmy pod uwagę dwie funkcje:
1
2
3
4
5
6
7
8
9
function foo($argc, $argv)
	{
	return;
	}

function ref(&$argc, &$argv)
	{
	return;
	}

I cztery przykłady ich wywołania:

1
2
3
4
5
6
7
$argc = 4;
$argv = array('arg', 'foo', 'bee');

foo($argc, $argv);   // na górze dwa "zwykłe" wywołania
foo(&$argc, &$argv); // na dole dwie referencje
ref($argc, $argv);   // jak dasz mi ponownie dwa zwykłe wywołania
ref(&$argc, &$argv); // to dam ci znów dwie referencje

Jak myślicie, co otrzymamy na wyjściu? Oczywiście cztery ostrzeżenia dotyczące wspomnianych referencji [po jednym dla każdego wystąpienia takiego “nielegalnego” przekazania zmiennej]:

1
2
3
4
5
6
7
Warning: Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of foo(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file in [file] on line 16

Warning: Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of foo(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file in [file] on line 16

Warning: Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of ref(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file in [file] on line 18

Warning: Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of ref(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file in [file] on line 18

Gdybyście pytali - linijki 16 oraz 18 to oczywiście:

1
2
foo(&$argc, &$argv); // 16
ref(&$argc, &$argv); // 18

Jak widać interpreterowi nie do końca spodobał się nasz “radosny” sposób przekazywania parametrów. Jak sobie z tym radzić?

Rozwiązanie.

Aby przekazywać zmienne jako referencje i nie martwić się wyrzucanym błędem możemy zmusić interpreter do polubienia naszego kodu na kilka sposobów.

Pierwszym z nich, najbardziej ofensywnym, jako, że nie zawsze mamy dostęp do konfiguracji serwera, jest edycja pliku php.ini. Zawarta jest w nim jedna interesująca nas linijka [wraz z komentarzem ;]]:

; - allow_call_time_pass_reference = Off [Cde cleanliness] ;     It's not possible to decide to force a variable to be passed by reference ;     when calling a function.  The PHP 4 style to do this is by making the ;     function require the relevant argument by reference.
Wystarczy odkomentować linijkę z ustawieniem, zmienić jego wartość na On i po sprawie. Oczywiście, jeśli nie mamy dostępu do php.ini, nie wszystko stracone. To samo ustawienie możemy zmienić za pomocą pliku .htaccess. Wystarczy, że umieścimy w nim linijkę:
php_flag allow_call_time_pass_reference On
Możemy też się po prostu "poddać" i usunąć wszelkie wystąpienia parametrów z referencjami z wywołań naszych funkcji, wtedy interpreter nie zaprotestuje niezależnie od ustawień. ;]

Jako ciekawostkę podam jeszcze jedno rozwiązanie - przekazanie argumentów jako tablicy z referencjami, przy czym należy pamiętać, że nie możemy przekazać referencji do tablicy:

1
2
foo(array(&$argc), array(&$argv));
ref(array(&$argc), array(&$argv));

W obu przypadkach przekazanie zmiennej $argv spowoduje:

Fatal error: Only variables can be passed by reference in [file]  on line [line]
Także nie próbujcie tego w domu. ;]

PHP.

Chciałbym zwrócić tutaj uwagę na jedną istotną rzecz. Ustawienie pozwalające na przekazywanie referencji w funkcjach jest domyślnie ustawione na Off, także automatycznie powstaje pytanie - dlaczego? W językach takich jak C czy C++ jest to normalna praktyka. W deklaracji funkcji oczywiście możemy ustawić odpowiedni poziom abstrakcji na jakim zostanie rozpatrzony przekazany argument, ale to do programisty należy wybór, czy chce on przekazać "zwykłą" zmienną, referencję, czy wskaźnik. Oczywiście wskaźników w PHP niestety nie przekażemy bo ich nie ma ;], ale dziwi mnie ta decyzja podjęta przez programistów.

Żeby być sprawiedliwym powiem jeszcze, że nie miałem zbyt wielu okazji aby użyć rzeczonych referencji w faktycznym kodzie PHP. Ze względu na fakt, że zmienna w tym języku nie jest tym samym, czym jest zmienna w przytoczonym wcześniej C++ [w pierwszym przypadku to tylko alias na wewnętrzną reprezentację w interpreterze, w drugim to już prawdziwy “kawałek pamięci”] nieczęsto musiałem sięgać do takich narzędzi.

Być może ktoś jednak wyprowadzi mnie z błędu, także będę wdzięczny za komentarze - w końcu człowiek uczy się całe życie. ;]

Podsumowanie.

Sesja zbliża się wielkimi krokami, coraz trudniej jest mi pracować nad czymkolwiek ambitnym, tym bardziej tworzyć wpisy na blogu. Mam nadzieję, że jednak przetrwam ten ciężki okres czasu i dam radę także w wakacje dalej tworzyć dla Was nową ciekawą zawartość. Do zobaczenia!