Data wypuszczenia PHP 8 przypada na 26 listopada 2020 roku. Po dużym przeskoku z wersji 5.6 na 7.0 przychodzi kolej na następne podniesienie oczka głównej wersji, tym razem o 1. Każda tego typu zmiana niesie ze sobą zarówno nowe funkcjonalności języka jak i porzucenie przestarzałych, uznanych za niepotrzebne czy modyfikacja dotychczas istniejących.
Na pierwszy rzut oka wydaje się, że trochę tego jest. Mimo to przyznam, że spora część to po prostu kosmetyka. Niestety nie zabrakło też zmian mogących negatywnie wpłynąć na jakość kodu – przynajmniej w moim odczuciu (o czym więcej poczytacie dalej). Nie przedłużając – co nowego w PHP 8 – start!
Zmiany rozwojowe
JIT (just-in-time compilation)
Tak zwana kompilacja kodu w locie. O tej zmianie chyba najgłośniej. Przyznam, że nie doktoryzowałem się w tym temacie, ale wierzę że faktycznie przekłada się ona na lepszą wydajność. Oby zmiana nie okazała się tylko czystym zabiegiem marketingowym niemającym wpływu na lepsze działanie PHP. Ostatecznie opcja ta jest opcjonalna i możliwa do wyłączenia.
Operator nullsafe
Co wersję pojawiają się nowe skrótowe składnie języka. Niektóre wydają mi się niezbyt czytelne, ale fakt faktem powodują, że koduje się dużo wygodniej. Operator nullsafe to żadna nowość w świecie technologii, ale niektóre języki nie wiadomo z jakich powodów jeszcze go nie wspierają. PHP od wersji 8 już do nich nie należy.
$formattedDate = $post->getStartDate()?->format('d.m.Y');
$formattedDate = $post->getStartDate() ? $post->getStartDate()->format('d.m.Y') : null;
Dwa powyższe kawałki kodu są identyczne w rezultacie, a różnią się jedynie zapisem. Składni ?->
można użyć zarówno na zmiennej jak i metodzie mogącej zwrócić wartość null. Przykład podany w RFC zawiera w sobie wiele więcej zagnieżdżeń, co pozwala skrócić kilka ifów do jednej linijki. Trzeba pamiętać, że takie wywołania (poza tak zwanym płynnym interfejsem) bardzo często sygnalizują niezgodność z Prawem Demeter (LoD).
Argumenty przekazywane po nazwie (Named Arguments)
public function create(?string $uuid = null, string $name, int $type) {}
$factory->create(
type: 1,
name: 'Test'
);
Przekazywanie argumentów metody po nazwie pozwala na zupełną dowolność jeśli chodzi o ich kolejność. Umożliwia także pominięcie parametrów opcjonalnych z wartością domyślną. Co akurat może być pomocne w przypadku zastanego kodu i masy opcjonalnych parametrów.
Mimo wszystko widzę więcej problemów, niż pożytku. Po pierwsze opcjonalne parametry często sygnalizują, że coś poszło nie tak. Po drugie rozwiązanie wydaje mi się mało czytelne. No i w końcu po trzecie – co w przypadku zmiany nazwy argumentu. Mam nadzieję, że inteligentne IDE sobie z tym poradzą.
Widać, że moje sceptyczne nastawienie do tej funkcjonalności nie jest czymś wyjątkowym, bo w głosowaniu to RFC także zebrało dużo negatywnych głosów. Mimo wszystko liczba pozytywnych wystarczyła żeby ten feature znalazł się w kolejnej wersji.
Funkcja match()
I jest też coś dla seniorów… Alternatywa dla konstrukcji switch()
. I to na całe szczęście lepsza alternatywa. Widzę wiele zastosowań w różnego rodzaju mapperach. Nie potrzebuje przerwania break
po każdym dopasowaniu, ale co ważniejsze opiera się o restrykcyjne porównanie (===), zamiast słabego (==).
Konstrukcja switch nie pilnuje też, czy udało się znaleźć dopasowanie. W przypadku funkcji match()
również można wprowadzić domyślne zachowanie, ale jeśli takiego nie ma oraz nie udało się dopasować argumentu – rzuca UnhandledMatchError
. Oczywiście funkcja działa z góry do dołu, więc zatrzyma się na pierwszym trafieniu.
$statusName = match ($status) {
0 => 'deleted',
1, 2, 3 => 'created',
4 => 'closed',
5 => 'canceled',
default => 'undefined'
};
Rzecz warta dodania – match staje się słowem zarezerwowanym. Co akurat jest zmianą mogącą powodować błędy przy podnoszeniu wersji jako, że słowo jest dość dobrze pasujące do wielu metod.
Typy łączone (Union Types)
Możliwość zastosowania więcej niż jednego typu dla parametru lub typu zwracanego. Do tej pory unię stworzyć można było tylko z typem null (?). I chyba to jedyna sensowna. Według nowej notacji przykładowo ?int
zapisany jest jako int|null
. Oczywiście obie są prawidłowe.
Myślę, że nie ma co za wiele komentować. Od razu powiem, że nie zamierzam korzystać z tego „udogodnienia”. Metoda z typem zwracanym int|float
od razu sygnalizuje mi, że coś zostało sknocone. Trzeba jednak wspomnieć o kilku kruczkach.
Oczywiście typ void
nie może być łączony z innym typem. Przy liczbie typów większej niż 2 (tak mogą takie być) null
musi być w postaci int|float|null
(nie ma szans na ?int|float
). Poza tym typ null
nadal może występować tylko w unii, a nie jako samodzielny. Zresztą podobnie sprawa ma się z nowym typem false
. Typ ten dopuszcza wyłącznie negatywny scenariusz typu bool
. Nie może więc wystąpić w zapisie bool|false
. Swoją drogą metoda, która może zwrócić wartość danego typu lub false
nie brzmi dobrze. Tak projektowane były stare funkcje PHP np. strpos()
.
Typ mieszany (mixed)
Ehh, za dużo już tych zmian na typach. Kolejna, która od razu powinna wylecieć do kosza. Może lepiej nie używać typów, niż wrzucać mixed
wszędzie gdzie popadnie. Chociaż oczywiście wszystko zależy od programisty i wierzę, że będzie tylko jako typ przejściowy albo faktycznie w miejscach, gdzie nie da się inaczej. W końcu TypeScript – czyli język bardzo mocno opierający się na typach też pozwala na różne odstępstwa. Co nie znaczy, że są dobrą praktyką.
Skrótowy zapis deklaracji pól klasy
Nic rewolucyjnego. Komu podpasuje to skorzysta, komu nie to zostanie przy zapisie klasycznym.
class FilterDTO
{
public string $searchPhrase;
public int $type;
public int $status;
public function __construct(
string $searchPhrase,
int $type,
int $status
) {
$this->searchPhrase = $searchPhrase;
$this->type = $type;
$this->status = $status;
}
}
class FilterDTO
{
public function __construct(
public string $searchPhrase,
public int $type,
public int $status
) {}
}
::class na obiekcie
Alternatywa dla metody get_class()
. Myślę, że fajne usprawnienie, a na pewno nikomu nie zaszkodzi.
$object = new \DateTime();
$object::class;
Prywatne metody różne od rodzica
To tak naprawdę naprawa nie do końca poprawnego działania. Wcześniej metody prywatne o tej samej nazwie, co metody w klasie rodzica musiały przyjąć taką samą sygnaturę. Co nie miało sensu, bo przecież zasięg prywatny ogranicza dziedziczenie.
Dozwolony przecinek po ostatnim parametrze funkcji
Do kompletu brakowało już tylko tej zmiany. We wcześniejszych wersjach zostały odblokowane przecinki na końcu tablicy czy przy wywoływaniu metody. Dla spójności taka zmiana ma sens – czasem zdarzy się zapomnieć o tych przecinkach, choć ja oczywiście pilnuję żeby ich zbędnie nie zostawiać.
Więcej metod statycznych do tworzenia obiektu daty
Dwie nowe funkcje do odtworzenia daty z już istniejącej.
\DateTime::createFromInterface(\DateTimeInterface $date);
\DateTimeImmutable::createFromInterface(\DateTimeInterface $date);
Łapanie wyjątków bez przymusowej zmiennej
Wcześniej nawet jeśli w bloku catch
nie używało się przechwyconego wyjątku trzeba było wrzucić go w zmienną. Obecnie wystarczy typ wyjątku, ale oczywiście pamiętajcie żeby nie zostawiać pustych bloków.
Interfejs Stringable
Interfejs wymuszający implementację metody magicznej __toString()
. Fajnie, że trafił do core, bo wcześniej trzeba było samemu o to zadbać lub ewentualnie wierzyć, że metoda zostanie zaimplementowana. Ja nie korzystam z niej zbyt często, ale dla osób które używają, interfejs okaże się pomocny.
Nowe funkcje do operacji na ciągach znaków
Trzy nowe funkcje wbudowane w PHP 8. Można będzie porzucić przestarzałe funkcje i wyrażenia regularne.
str_contains('hello world', 'hell'); // true
str_starts_with('hello world', 'he'); // true
str_ends_with('hello world', 'rld'); // true
Abstrakcyjne metody prywatne w traitach
Nie przepadam za traitami i raczej ich nie używam. W wersji 8 wprowadzają abstrakcyjne metody prywatne. Mam nadzieje, że nie będą używane – żadna to abstrakcja. Od tego są przede wszystkim interfejsy i ewentualnie klasy abstrakcyjne.
Funkcja get_debug_type()
Lepsza alternatywa dla funkcji gettype()
. Działa poprawnie dla obiektów i typów skalarnych.
Zmiany BC
– Zmiany w raportowaniu błędów, ostrzeżeń i notek
Sporo w tej wersji zmian związanych z błędami. Znalazło się tutaj kilka uspójnień i zmian – raczej w kierunku bardziej restrykcyjnego podejścia.
– Zmiana domyślnego raportowania błędów
Zmiana domyślnego raportowania błędów na E_ALL
. Wcześniej domyślnie wykluczone były informacje o notice i deprecated. Oczywiście nadal można przestawić na stary tryb.
– Fatal error silniejszy niż @
Małpa już nie wycisza fatal errorów. Mam nadzieję, że już nikt tego nie używa, powinni to całkowicie wywalić w kolejnej wersji.
– Uspójnienie błędów dla wbudowanych funkcji
Funkcje tworzone przez programistę rzucały TypeError, za to wbudowane funkcje PHP emitowały ostrzeżenia. Uspójniono w stronę wyjątków – jak najbardziej na plus.
– Nowa kolejność działań przy konkatenacji
echo "result: " . $x + $y;
echo ("result: " . $x) + $y; // < PHP 8.0
echo "result: " . ($x + $y); // > PHP 8.0
Mimo wszystko zalecam używać nawiasów dla lepszej czytelności.
– Obsługa typów w metodach magicznych
__call(string $name, array $arguments): mixed
__callStatic(string $name, array $arguments): mixed
__clone(): void
__debugInfo(): ?array
__get(string $name): mixed
__invoke(mixed $arguments): mixed
__isset(string $name): bool
__serialize(): array
__set(string $name, mixed $value): void
__set_state(array $properties): object
__sleep(): array
__unserialize(array $data): void
__unset(string $name): void
__wakeup(): void
– Display_startup_errors domyślnie włączone
– Konstruktor wyłącznie przy użyciu __construct()
– Dostęp do niezdefiniowanej stałej rzuci error zamiast warninga
Podsumowanie zmian w PHP 8
Oczywiście to nie wszystko. Jest jeszcze kilka mniejszych zmian, bądź takich które ja uznałem za niewarte poruszania. Pełny zakres zmian dostępny pod linkiem. Podziękowania również dla Brenta, który na swoim blogu zebrał to wszystko do kupy: https://stitcher.io/blog/new-in-php-8 i nie ukrywam, że też pomogło mi to przygotować wpis dla Was.
Fajne opracowanie. Dzięki. Uprawiam jeszcze kod który ma działać w wersji 5.6, a tu już 8 za pasem :). Oczywiście w wyższych wersjach również już pisałem, ale jak się na to patrzy z tej perspektywy to zdecydowanie łatwiej jest wymyślać nowe zjawiska i relacje w języku niż je implementować w realnych projektach czy długoletnich projektach .
Myślę, że atrybuty https://wiki.php.net/rfc/attributes_v2 są warte wspomnienia 🙂
Ale, że bez nawet wspominki o atrybutach? 😮
Faktycznie, pominąłem je zamierzenie. Na ten moment wszelkie meta dane są oznaczane jako adnotacje/docblocksy. Odsyłam jednak do wpisu: https://php.watch/articles/php-attributes jeśli kogoś interesuje temat.
„Widać, że moje sceptyczne nastawienie do tej funkcjonalności”
–moim-skromnym-zdaniem–>
„Widać, że moje sceptyczne nastawienie do tej funkcji”
🙂
„Po pierwsze opcjonalne parametry często sygnalizują, że coś poszło nie tak.”
dlaczego opcjonalne parametry są bee?
Metody są wtedy nieprecyzyjne, a sam interfejs klasy nieczytelny. Lepiej często rozbić to na kilka metod, bardziej obrazujących zachowania obiektu. Jak napisałem „często”, co nie oznacza żeby w ogóle nie używać opcjonalnych parametrów – w wielu przypadkach robią robotę.