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

Języki skryptowe mają to do siebie, że dzięki wprowadzonej abstrakcji interpretera możliwa jest o wiele szersza ingerencja w sposób działania już uruchomionego skryptu. PHP jest jednym z takich języków, stąd czasem potrafi zadziwić swoimi możliwościami, dając nam narzędzia zarówno bardzo pomocne jak i proste w użyciu. W niniejszym wpisie chciałbym Wam przedstawić jedno takie narzędzie - funkcje o zmiennej liczbie parametrów.

Wstęp.

Jeśli mieliście kiedykolwiek do czynienia z językiem C lub C++ pewnie zastanawialiście się jak działa np. funkcja printf(). Jeśli nie, to musicie jedynie wiedzieć, że funkcja ta po prostu wypisywała na standardowe wyjście żądany ciąg znaków. Nie ma w tym żadnej magii, poza tym, że mogliśmy podać dowolną ilość parametrów dowolnych typów [oczywiście musieliśmy też uwzględnić je w łańcuchu formatującym], a funkcja cały czas działała poprawnie.

Każdemu programiście, który napisał w życiu co najmniej jedną funkcję a nie słyszał o mechanizmach variadic functions [funkcje wariadyczne? chyba lepiej brzmi po angielsku ;]] wyda się to zapewne niemożliwe. Zresztą, zobaczcie sami:

1
2
printf("%s %i", "string", 24); // wypisze na ekran "string 24"
printf("%s %i %f", "other", 51, 5.43); // wypisze na ekran "other 51 5.43"

Pamiętam, jak na samym początku przygody z programowaniem próbowałem “imitować” jej działanie poprzez przeciążanie wielu funkcji o tej samej nazwie, różniących się jedynie ilością parametrów. Powstawały wtedy “potworki” w stylu:

1
2
3
4
5
void myprintf(char *format, int a);
void myprintf(char *format, int a, int b);
void myprintf(char *format, int a, int b, int c);
void myprintf(char *format, int a, int b, int c, int d);
void myprintf(char *format, int a, int b, int c, int d, int e);

Oczywiście te funkcje miały jakieś zastosowanie, więc nie myślcie, że wyważałem otwarte drzwi. Po prostu na początku nie miałem świadomości istnienia pewnych mechanizmów i sztuczek, stąd trzeba było sobie radzić z tym, co się umiało. Ze względu na to, że rzadko podawałem więcej niż 5 argumentów działało to całkiem nieźle. ;] Wracając do tematu - pewnie domyślacie się teraz, że pokażę Wam jak osiągnąć tego typu efekt w języku PHP. Nie wiem jakim cudem, ale zgadliście. ;]

func_*

Aby kontrolować liczbę i wartości podanych argumentów potrzebne będą odpowiednie narzędzia. Programiści biblioteki standardowej PHP oddali do naszego użytku aż trzy specjalnie przygotowane funkcje, są to: Nazwy są bardzo intuicyjne, a działanie tych funkcji przejrzyste, stąd bardziej rozbudowany komentarz wydaje mi się zbędny. Tradycyjnie najlepiej będzie zobaczyć je wszystkie "w akcji". Przygotowałem dla Was przykładową funkcję, która informuje użytkownika o podanych do niej parametrach:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<pre>
<?php
error_reporting(E_ALL | E_STRICT);

function foo()
	{
	static $calls = 0;
	$num = func_num_args();
	$args = func_get_args();
	if(!$calls)
		{
		echo 'Hello, my name is Foo.<br />';
		}
	else
		{
		echo 'Hello, my name is Foo again.<br />';
		}
	$calls++;
	if(!$num)
		{
		echo 'You were mean and passed no arguments.<br />';
		}
	else
		{
		echo 'You were quite generous and passed '.$num.' arguments.<br />';
		echo 'These arguments were:<br />';
		echo 'print_r(), for sure:<br />';
		print_r($args);
		echo 'foreach():<br />';
		foreach($args as $key => $val)
			{
			echo 'Argument '.$key.' has value: "'.func_get_arg($key).'"<br />';
			}
		}
	echo '<br />';
	return;
	}

foo();
foo(12, 24);
foo('string', 4.56, 52, true, NULL);
?>
</pre>

Wynik wykonania powyższego skryptu jest następujący:

Hello, my name is Foo.
You were mean and passed no arguments.

Hello, my name is Foo again.
You were quite generous and passed 2 arguments.
These arguments were:
print_r(), for sure:
Array
(
    [0] => 12
    [1] => 24
)
foreach():
Argument 0 has value: "12"
Argument 1 has value: "24"

Hello, my name is Foo again.
You were quite generous and passed 5 arguments.
These arguments were:
print_r(), for sure:
Array
(
    [0] => string
    [1] => 4.56
    [2] => 52
    [3] => 1
    [4] =>
)
foreach():
Argument 0 has value: "string"
Argument 1 has value: "4.56"
Argument 2 has value: "52"
Argument 3 has value: "1"
Argument 4 has value: ""

Zwróćcie uwagę na jedną rzecz - w nagłówku funkcji foo() nie został podany ani jeden argument formalny! Za pomocą powyższych trzech funkcji jesteśmy więc w stanie dowolnie operować na tym, co zostało faktycznie przekazane, widać to dokładnie w sposobie wywołania:

1
2
3
foo();
foo(12, 24);
foo('string', 4.56, 52, true, NULL);

Zarówno “puste” wywołanie, jak i te bardziej “hojne” zostały poprawnie obsłużone - w naszych skryptach możemy oczywiście zareagować w całkowicie dowolny sposób. Jednym ze sposobów jakie przyszły mi do głowy podczas tego wpisu może być np. stworzenie funkcji, która będzie w różny sposób reagowała na ilość podanych parametrów:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function switcher()
	{
	switch(func_num_args())
		{
		case 0:
			{
			// code
			}
		case 1:
			{
			// code
			}
		case 2:
			{
			// code
			}
		// (...)
		default:
			{
			// code
			}
		}
	return;
	}

Ogranicza nas jedynie wyobraźnia. ;]

Zalety i wady.

Zaletą takiego rozwiązania jest oczywiście możliwość większej kontroli nad kodem. W pewnych sytuacjach możemy chcieć np. "skompresować" kilka funkcji w jednej bazując na ilości / typach podanych parametrów - mając wiedzę zdobytą w tym wpisie możemy zrobić to w miarę prosto i bezboleśnie.

Największą wadą zastosowania tych funkcji będzie ograniczenie czytelności kodu. Mówiąc o czytelności mam w tym wypadku nie samą nieznajomość działania, ale to, że “nie widać” [wymagane jest napisanie oddzielnej dokumentacji jakie wywołania odpowiadają poszczególnym akcjom] jakie parametry są wymagane do jej wykonania. Można to częściowo ominąć nazywając część parametrów i podając w nagłówku ich wartości domyślne:

1
2
3
4
function foo($arg1 = NULL, $arg2 = NULL)
	{
	// (...)
	}

Oczywiście wartości domyślne są opcjonalne, ale jeśli ich nie podamy to “zepsujemy” puste wywołanie funkcji. W każdym razie umieszczając pewne parametry jawnie w deklaracji możemy się do nich odwołać na dwa sposoby.

W mojej opinii o wiele mniejszym, ale jednak problemem może być zaprzestanie wspierania tych funkcji przez przyszłe wersje PHP. Język ten od początku rozwija się w bardzo burzliwy sposób, to dodając, to usuwając pewne funkcjonalności, stąd zawsze trzeba mieć na uwadze fakt, że kiedyś komuś może się przestać podobać takie rozwiązanie i po prostu “wyrzuci” je z kodu. Jest to bardzo mało prawdopodobne, ale w programowaniu jest jedna prosta zasada: “nigdy nie mów nigdy”. ;]

Podsumowanie.

Mam nadzieję, że powyższy wpis pomoże Wam w tworzeniu własnych ciekawych funkcji przyjmujących tyle parametrów ile tylko dusza zapragnie. Wspomniałem na początku o C i C++ - myślę, że w przyszłości na pewno pojawi się wpis o tym rozwiązaniu w tych językach. Tymczasem życzę miłego wieczoru i do zobaczenia w niedzielę w kolejnym Linkdumpie. ;]