Podpowiadanie typów w PHP

PHP nie wymaga precyzowania typu podczas definicji zmiennej. Jest on określany na podstawie kontekstu, w jakim ta zmienna została użyta, z możliwością automatycznych konwersji. Możemy także samodzielnie zmieniać typ zmiennej poprzez rzutowanie lub odpowiednie funkcje. Rodzi to problemy szczególnie przy przekazywaniu parametrów do funkcji, jednak w tym przypadku można dla niektórych stosować podpowiadanie typów (ang. type hinting).

W PHP mamy do dyspozycji łącznie 9 typów zmiennych:

  • 4 typy proste – logiczny, całkowity, zmiennoprzecinkowy i znakowy
  • 2 złożone – tablicowy i obiektowy
  • 3 specjalne – identyfikator zasobu (resorce), pusty (NULL) oraz wywoływalny (callable)

Podpowiadanie typów w PHP nie umożliwia niestety rozpoznawania wszystkich wymienionych powyżej. Od wersji 5 języka możemy to robić dla obiektów, interfejsów, tablic (od PHP 5.1), oraz parametrów wywoływalnych (PHP 5.4).

Podpowiadanie typów możemy stosować w zwykłych funkcjach, oraz metodach klas. Wystarczy w tym celu przed nazwą zmiennej podać oczekiwany typ (array, callable, nazwę konkretnej klasy lub interfejsu)

Podpowiadanie typów – obiekty

W przypadku podpowiadania typów obiektowych należy pamiętać o bardzo ważnej rzeczy: jeśli parametrem musi być konkretna klasa lub interfejs, dozwoleni są także ich potomkowie, uczestniczący w hierarchii dziedziczenia.

Gdy zostanie określony wymagany typu obiektu, przekazanie innego spowoduje powstanie błędu. Zostało to pokazane na poniższym przykładzie:

class Factory{
	public function produce(Product $product){
		print "Produkuję " . get_class($product);
	}
}

class Stock{
	/* ... */
}

class Product{
	/* ... */
}

class Car extends Product{
	/* ... */
}

class Motorbike extends Product{
	/* ... */
}

$factory = new Factory();
$stock = new Stock();
$car = new Car();
$motorbike = new Motorbike();

$factory->produce($car);
$factory->produce($motorbike);
$factory->produce($stock);

Zdefiniowane zostały klasy: Factory, Stock, Product oraz dziedziczące po nim Car oraz Motorbike. Klasa fabryki posiada zdefiniowaną jedną metodę, odpowiadającą za produkcję. Wymaga ona przekazania obiektu typu Product. Na powyższym listingu przekazano do niej natomiast dozwolone parametry wywodzące się z klasy Product, ale na koniec omyłkowo także Stock. Efekt będzie następujący:

Produkuję Car
Produkuję Motorbike
Fatal error: Argument 1 passed to Factory::produce() must be an instance of 
Product, called in typehinting.php on line 31 and defined in typehinting.php 
on line 3

Jak wspomniano już wcześniej, podpowiadanie typów w przypadku obiektów działa zarówno dla utworzonych bezpośrednio, jak i dziedziczących po określonym w definicji. Poprawne będzie więc też następujące wywołanie:

$factory = new Factory();
$product = new Product();

$factory->produce($product);

W powyższym przypadku może nie jest to do końca pożądane, jednak w praktyce częściej będziemy oczekiwali obiektów klas dziedziczących. Oczywiście, gdy tego chcemy, możemy zawęzić wymagany typ tylko do klasy pochodnej.

class Factory{
	public function produce(Car $product){
		print "Produkuję tylko samochody";
	}
}

W przypadku podpowiadania typów można stosować także wartości domyślne. Jeśli w naszej fabryce przewidujemy strajk, możemy posłużyć się następującą definicją:

class Factory{
	public function produce(Product $product = null){
		print "Produkuję" . get_class($product);
	}
}

$factory = new Factory();
$factory->produce();

Analogicznie do powyższych informacji, podpowiadanie typów może być używane także w przypadku interfejsów. Wystarczy określić, że parametr musi implementować jeden ze zdefiniowanych. Aby zobrazować tą możliwość, dokonamy pewnych zmian klas z pierwszego przykładu. Otóż w naszej fabryce w Indiach zamierzamy wyprodukować limitowaną wersję samochodu, o zwiększonej mocy silnika. Klasa fabryki otrzyma więc dodatkową możliwość produkcji, a specyfikacja samochodu zostanie odpowiednio zmodyfikowana poprzez implementację interfejsu (pominięto tu klasy zdefiniowane w pierwszym przykładzie):

interface limitedEdition{
	public function engineImprovements();
}

class carSpecial extends car implements limitedEdition{
	public function engineImprovements(){
		print "Zwiększona moc silnika";
	}
}

class FactoryIndia extends Factory {
	public function produceSpecialCar(limitedEdition $car){
		print "Produkuję limitowaną wersję samochodu.";
	}
}

$factoryIndia = new FactoryIndia();
$car = new car();
$carSpecial = new carSpecial();

$factoryIndia->produceSpecialCar($carSpecial);
$factoryIndia->produce($car);
$factoryIndia->produceSpecialCar($car);

Efekt wywołania kodu będzie następujący:

Produkuję limitowaną wersję samochodu.
Produkuję Car
Fatal error: Argument 1 passed to FactoryIndia::produceSpecialCar() must implement 
interface limitedEdition, called in typehinting.php5 on line 43 and defined in 
typehinting.php on line 32

Jak widać, najpierw wyprodukowano edycję limitowaną samochodu przy użyciu nowej linii produkcyjnej, a następnie powrócono do wersji zwykłej. W kolejnym kroku, w wyniku błędu pracownika, uruchomiono jednak ponownie zmodyfikowaną linię produkcyjna dla wersji podstawowej samochodu, co oczywiście zostało szybko wychwycone przez inspektorów kontroli jakości.

Podpowiadanie typów – tablice

Sytuacja jest analogiczna do opisywanych powyżej możliwości w przypadku obiektów. Poniższy przykład obrazuje w jaki sposób zastrzec, aby zdefiniowana funkcja akceptowała jako parametr tylko tablicę:

function needArray(array $param){
	print_r($param);
}

$array = array(1, 2, 3);

needArray($array);

Możemy także określić domyślą tablicę, dzięki czemu możliwe będzie wywołanie funkcji bez żadnego parametru:

function needArray(array $param = array(7, 8, 9)){
	print_r($param);
}

Podpowiadanie typów – callable

Jest to najnowsza możliwość podpowiadania typów, wprowadzona w PHP 5.4. Dzięki niej można określić, że parametr przekazywany do funkcji lub metody musi być wywoływalny. Ma to zastosowanie w przypadku stosowania callbacków oraz funkcji anonimowych.

function myFunction(callable $callback) {
	echo "Wywołuję callback: " . $callback();
}

myFunction(function() { return 'Witaj!'; });

Podsumowanie

Jak widać na przedstawionych przykładach, wykorzystanie podpowiadania typów jest niezwykle przydatne i znacznie upraszcza tworzony kod. Nie musimy aż tak wnikliwie sprawdzać przekazywanych argumentów przy pomocy is_array() lub instanceof. Mam nadzieję, że w type hinting w kolejnych wersjach PHP zostanie rozszerzone o typy proste.

Dodaj komentarz