PHP 8 – co nowego i wartego uwagi

liczba 8

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.

Krystian Żądło
Programista PHP i właściciel marki Koddlo. Pasjonat czystego kodu i dobrych praktyk programowania obiektowego. Prywatnie fan angielskiej piłki nożnej, dobrego humoru oraz podcastów.