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:

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:

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:

<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:

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:

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:

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. ;]