Jeżeli zastanawiałeś się co może oznaczać @return T bądź @template T, ten wpis może być dla Ciebie! Rzućmy okiem co to jest. Mianowicie są to generyki! Choć nie są w pełni natywne w PHP, mają na celu umożliwienie programistom pisania kodu, który może działać z różnymi typami danych w sposób typowany i bezpieczny.
PHP wprowadziło ograniczoną obsługę generyków poprzez mechanizmy takie jak typowanie argumentów i zwracanych wartości oraz opisy w adnotacjach PHPDoc, co pozwala na bardziej precyzyjne informowanie o typach danych podczas dokumentowania kodu.
O co chodzi w generykach w PHP?
Generyki to technika programowania, która pozwala na tworzenie klas, interfejsów i funkcji, które mogą działać na różnych typach danych, zamiast być zablokowane na jeden konkretny typ. Generyki są szczególnie przydatne w strukturach danych, takich jak kolekcje (tablice, listy itp.), ponieważ pozwalają uniknąć powielania kodu dla różnych typów danych.
Zamiast np. tworzyć osobne klasy ListOfStrings i ListOfIntegers, można stworzyć ogólną klasę List, która może przyjmować dowolny typ elementów.
Użycie generyków w PHPDoc
Choć PHP nie ma w pełni natywnych generyków jak w Javie czy C#, można używać generyków w adnotacjach PHPDoc, aby dostarczać narzędziom analizy statycznej (np. PHPStan, Psalm) oraz edytorom bardziej precyzyjnych informacji na temat typów danych. Dzięki temu IDE może podpowiadać typy, ostrzegać o błędach i zapewniać lepszą weryfikację typów podczas pisania kodu.
Przykłady generyków w PHPDoc
Przykład 1: Lista obiektów
/** * @template T * @param T[] $items Lista elementów typu T * @return T Pierwszy element z listy */ function getFirstElement(array $items) { return $items[0]; }
W tym przykładzie @template T definiuje generyczny typ T, który może być dowolnym typem przekazanym w tablicy. Funkcja getFirstElement() zwraca pierwszy element z tej tablicy, który jest tego samego typu, co elementy w tablicy.
Wynik:
// Przykład dla tablicy liczb całkowitych $intArray = [1, 2, 3]; $firstInt = getFirstElement($intArray); echo $firstInt; // Zwróci: 1 // Przykład dla tablicy łańcuchów znaków $stringArray = ['apple', 'banana', 'cherry']; $firstString = getFirstElement($stringArray); echo $firstString; // Zwróci: 'apple'
Przykład 2: Klasa generyczna
Tutaj @template T definiuje generyczny typ T dla całej klasy Collection. Dzięki temu można stworzyć kolekcję różnych typów obiektów (np. Collection<int>, Collection<string>, Collection<Foo>).
Przykład 3: Użycie generyków z określeniem typu
/** * @template T of SomeClass */ class Repository { /** @var T[] */ private array $items; /** * @param T[] $items */ public function __construct(array $items) { $this->items = $items; } /** * @return T[] */ public function getAll(): array { return $this->items; } }
W tym przypadku generyk T jest ograniczony do typu SomeClass, co oznacza, że kolekcja może przechowywać jedynie instancje tej klasy lub jej podklas.
Przykład działania
Poniżej przedstawiam przykładową implementację klasy Collection oraz sposób jej użycia:
/** * @template T */ class Collection { /** * @var T[] */ private array $items; /** * @param T[] $items */ public function __construct(array $items = []) { $this->items = $items; } /** * @return T[] */ public function getItems(): array { return $this->items; } /** * Dodaj element do kolekcji * * @param T $item * @return void */ public function add($item): void { $this->items[] = $item; } /** * Pobierz element po indeksie * * @param int $index * @return T|null */ public function get(int $index) { return $this->items[$index] ?? null; } }
i sposób użycia:
// Kolekcja typu int $intCollection = new Collection([1, 2, 3]); $intCollection->add(4); var_dump($intCollection->getItems()); // Zwróci [1, 2, 3, 4] // Kolekcja typu string $stringCollection = new Collection(['a', 'b', 'c']); $stringCollection->add('d'); var_dump($stringCollection->getItems()); // Zwróci ['a', 'b', 'c', 'd']
Wyjaśnienie:
- @template T – to oznaczenie generyka, który jest miejscem na dowolny typ. W tym przypadku T może być np. int, string lub inny typ.
- @param T[] $items – parametr konstruktora to tablica elementów typu T.
- @return T[] – metoda getItems() zwraca tablicę elementów typu T.
- @param T $item – metoda add() dodaje element typu T do kolekcji.
- @return T|null – metoda get() zwraca element typu T lub null, jeśli element o danym indeksie nie istnieje.
Zastosowanie generyków w PHP
Generyki w PHP są przydatne do:
- Pisania kodu wielokrotnego użytku: Jedna klasa lub funkcja może działać z różnymi typami danych.
- Typowania kolekcji: Unikasz pisania osobnych klas dla różnych typów elementów w kolekcjach.
- Lepszej analizy statycznej: Narzędzia takie jak PHPStan lub Psalm mogą lepiej analizować typy w kodzie.
- Dokumentacji: Ułatwiasz czytanie i zrozumienie kodu przez innych programistów, ponieważ wiadomo, jaki typ danych jest obsługiwany.
Ograniczenia generyków w PHP
- Brak natywnej obsługi: PHP nie ma generyków na poziomie języka, jak ma to miejsce np. w Javie. Generyki są tutaj realizowane poprzez PHPDoc, co działa tylko z narzędziami analizy statycznej.
- Brak kontroli w czasie wykonywania: Generyki w PHP nie są sprawdzane w czasie działania, co oznacza, że błędy typów mogą pojawić się dopiero przy użyciu narzędzi takich jak PHPStan, a nie bezpośrednio podczas wykonywania skryptu.
Pytania, które pewnie przychodzą Ci teraz do głowy
Czy muszę używać generyków, jeśli używam deklaracji typu zwracanego string|int?
Nie, jeśli używasz deklaracji typu zwracanego string|int, PHP sprawdzi poprawność typów w czasie działania programu. Jednak ograniczasz się do sytuacji, w których funkcja będzie działać tylko z tymi dwoma typami.
Jeśli chciałbyś, aby funkcja mogła działać z dowolnym typem danych (np. obiektami, tablicami), generyki byłyby lepszym rozwiązaniem.
Jakie są korzyści z generyków w porównaniu do zwykłej deklaracji typu?
Generyki oferują większą elastyczność i możliwość pracy z różnymi typami danych, a jednocześnie zapewniają bezpieczeństwo typów. Na przykład, funkcja z deklaracją string|int będzie działać tylko dla tych dwóch typów, ale funkcja z generykami może obsłużyć dowolny typ, co zwiększa jej uniwersalność i ułatwia ponowne użycie.
Jak generyki zabezpieczają nasz kod?
Generyki zabezpieczają kod poprzez wymuszanie na programiście podawania właściwych typów danych. Dzięki nim możemy napisać funkcje, które działają na różnych typach, ale nadal będą weryfikować zgodność typów w czasie analizy statycznej lub kompilacji.
/** * @template T * @param T[] $items Lista elementów typu T * @return T Pierwszy element z listy */ function getFirstElement(array $items) { return $items[0]; } $numbers = [1, 2, 3]; $firstNumber = getFirstElement($numbers); // Zwróci int $strings = ['a', 'b', 'c']; $firstString = getFirstElement($strings); // Zwróci string $objects = [new stdClass(), new stdClass()]; $firstObject = getFirstElement($objects); // Zwróci stdClass
Dzięki temu możemy napisać uniwersalne rozwiązanie, które działa z każdym typem danych, zachowując przy tym bezpieczeństwo typów i ułatwiając ponowne wykorzystanie kodu.
Podsumowanie
Używanie generyków w PHP pozwala na tworzenie bardziej uniwersalnych i bezpiecznych rozwiązań, które działają z wieloma typami danych. Warto sięgnąć po nie w sytuacjach, gdy chcemy, aby funkcje lub klasy były bardziej elastyczne i mogły pracować z różnymi typami, ale jednocześnie zachować bezpieczeństwo typów i możliwość wczesnego wykrywania błędów.
Nikt jeszcze nie komentował. Bądź pierwszy!