Zmiany i nowości w PHP 8
Nowy PHP nadchodzi wielkimi krokami. Już na dzień 28 listopada zostało zapowiedziane wydanie 8-mej wersji tego popularnego języka programowania. Jakie zmiany i nowości zostaną wdrożone? Czy będą to zmiany na plus, czy może na minus? Tego wszystkiego dowiecie się z lektury niniejszego artykułu.
JIT (kompilator Just in Time)
Najbardziej reklamowaną nowością w PHP 8 jest kompilator JIT, który w znaczący sposób ma przyśpieszyć działanie oprogramowania, napisanego w oparciu o nową wersję PHP. Zeev Surasky, jeden z twórców tego rozwiązania, na dowód że wydajność oprogramowania znacząco rośnie, udostępnił test generowania fraktali, na którym widać porównanie jak działa oprogramowanie z i bez JIT.
Demo testowe faktycznie robi wrażenie, mam jednak wątpliwość, czy JIT będzie miał zastosowanie podczas codziennej pracy z językiem PHP przy tworzeniu stron lub serwisów internetowych. Podczas pisania „normalnych” aplikacji, zazwyczaj wąskim gardłem szybkiego działania aplikacji nie jest sam kod, ale nieprawidłowa integracja z bazą danych lub ładowanie zbyt dużych zasobów, dlatego samo przyśpieszenie kodu niewiele zmieni. Być może JIT znajdzie swoje zastosowanie w systemach przetwarzających jednorazowo duże ilości danych.
Łączenie typów
Łączenie typów (union types) pozwala na zdefiniowanie kilku typów jakie może przyjąć zmienna lub jakie może zwrócić funkcja. Do tej pory można było zdefiniować jedynie jeden typ, ewentualnie z możliwością zwrócenia null.
1 2 3 4 |
public function foo(Foo|Bar $input): int|float { // do something... } |
Typy zmiennych, które mogą przyjmować również null, mogą być zapisywane tak samo jak do tej pory (poprzez poprzedzenie typu znakiem „?”) lub w następującej formie:
1 2 3 4 |
public function foo(Foo|null $foo): void { // do smoething ... } |
Osobiście uważam, że o ile typowanie zmiennych wprowadzone w PHP 7 było dużym krokiem na przód w kontekście pisania bardziej spójnego i czytelnego oprogramowania, o tyle możliwość łączenia typów jest krokiem wstecz i nie będzie poprawiała jakości tworzonego kodu.
Operator nullsafe
Bardzo często podczas pobierania właściwości obiektu lub wywołania metody, nie mamy pewności czy nie zwrócona zostanie nam wartość null.
1 2 3 4 5 6 7 8 |
$user = $request->getUser(); $address = $user ? $user->getAddress() : null; $city = null; if ($address) { $city = $address ? $address->getCity() : null; } |
Operator nullsafe pozwoli skrócić powyższy kod do jednej linijki.
1 |
$city = $request->getUser()?->getAddress()?->getCity(); |
Na każdym etapie wywołania metod, będzie sprawdzane czy zwracaną wartością jest null i jeżeli tak, to dalsze wywołania zostaną przerwane.
Nazywanie argumentów funkcji
Nazywanie argumentów (named arguments) pozwoli na przekazywanie parametrów funkcji za z wykorzystaniem nazw zmiennych. Dzięki tremu ich kolejność nie ma znaczenia oraz umożliwia pominięcie parametrów opcjonalnych.
1 2 3 4 5 6 7 8 9 |
function foo(string $bar, string $baz, ?string $bom = null) { // do something ... } foo( baz: 'value baz', bar: 'value bar' ); |
Takie wywołanie funkcji ma swoje wady i zalety. Do zalet można zaliczyć to, że patrząc na takie wywołanie funkcji od razu widzimy, jakie parametru ona przyjmuje (oczywiście pod warunkiem, że zmienne są czytelnie i zrozumiale nazwane). Co jednak, gdy w deklaracji funkcji zmienimy nazwę któregoś z argumentów? W takim wypadku musimy również zmienić wszystkie wywołania tej funkcji. Ponadto możliwość przekazywania argumentów za każdym razem w innej kolejności, może pogarszać czytelność kodu.
Wyrażenie dopasowania
Nowe wyrażenie dopasowania (match expression) jest podobne do wyrażenia switch, tzn umożliwia wykonanie wielu instrukcji warunkowych bez użycia wielu instrukcji if, else, itp. Wyrażenie match wykonuje tzw. twarde porównania, to znaczy porównywana jest nie tylko wartość ale również typ porównywanych danych (switch sprawdzał jedynie wartość). Ponadto składnia wyrażenia jest nieco uproszczona, np. nie ma potrzeby użycia słowa break.
1 2 3 4 5 6 7 |
$number = 3; $result = match($number) { 0 => "action 1", 1, 2 => "action 2", 3 => "action 3", default => "default action" }; |
Należy pamiętać, że od wersji PHP 8 słowo „match” będzie zastrzeżone. Ma to szczególnie znaczenie w sytuacji migracji kodu napisanego dla starszej wersji języka, w którym mogłaby znaleźć się taka nazwa zmiennej, funkcji lub metody.
Parametry konstruktora automatycznie mapowane na właściwości klasy
W PHP 8 możemy deklarować konstruktory, które automatycznie tworzą właściwości klasy na podstawie przekazywanych parametrów. Najlepiej zobaczyć to na przykładzie.
1 2 3 4 5 6 7 8 9 |
class Address { public function __construct( public string $city, public string $street, public string $houseNumber ) { } } |
Powyższy kod jest równoznaczy z:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Address { public $city; public $street; public $houseNumber; public function __construct(string $city, string $street, string $houseNumber) { $this->city = $city; $this->street = $street; $this->houseNumber = $houseNumber; } } |
Jak widzimy, nowa składnia znacznie zmniejsza konieczność pisanego kodu.
Wyrzucanie wyjątków jako wyrażenie (throw expression)
W nowym PHP throw zmienia się z instrukcji w wyrażenie, co umożliwia jego wykorzystanie w kilku nowych miejscach w kodzie.
1 2 3 |
$triggerError = fn () => throw new MyError(); $foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset'); |
Przechwycony wyjątek nie musi być przypisywany do zmiennej
Czasami zdarza się, że nie potrzebujemy używać zmiennej do której przypisaliśmy przechwycony wyjątek. W starszych wersjach PHP wyjątek zawsze musiał być przypisany do zmiennej.
1 2 3 4 5 |
try { // Here some exception is thrown } catch (MyException $exception) { Log::error("Log error"); } |
Od teraz możemy tą zmienną pominąć.
1 2 3 4 5 |
try { // Here some exception is thrown } catch (MyException) { Log::error("Log error"); } |
Interfejs Stringable
Każda klasa implementująca interfejs Sringable, musi mieć utworzona metodę __toString(). Jeżeli mamy sytuację odwrotną, tzn. jeżeli klasa posiada już tą metodę, to taka klasa z automatu implementuje również interfejs Stringable i nie ma potrzeby jego ręcznego dodawania.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Foo { public function __toString(): string { return 'foo'; } } function bar(string|Stringable $stringable) { /* … */ } bar(new Foo()); bar('abc'); |
Ulepszenie metod abstrakcyjnych w trait
W języku PHP istnieje możliwość deklaracji abstrakcyjnych metod w trait, a następnie konieczność implementacji tych metod w klasach, które używają tak napisanego trait-a. Jednak w obecnych wersjach, sprawdzanie poprawności implementacji metody abstrakcyjnej nie do końca było poprawne. Na przykład, kod przedstawiony poniżej przechodził bez błędów:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
trait Test { abstract public function test(int $input): int; } class UsesTrait { use Test; public function test($input) { return $input; } } |
W PHP 8 taki kod musi być poprawiony do postaci:
1 2 3 4 5 6 7 8 9 |
class UsesTrait { use Test; public function test(int $input): int { return $input; } } |
Nowe funkcje
str_contains() – ulepszona wersja funkcjo strpos(), pozwala na sprawdzenie czy wybrany ciąg znajduje się w wskazanym tekscie
1 |
if (str_contains('string with lots of words', 'words')) { /* … */ } |
str_starts_with() i str_ends_with() – umożliwiają sprawdzenie czy dany tekst zaczyna lub kończy się w określony sposób
1 2 |
str_starts_with('haystack', 'hay'); // true str_ends_with('haystack', 'stack'); // true |
get_debug_type() – ulepszona wersja funkcji gettype(), zwraca typ zmiennej
get_resource_id() – zwraca identyfikator przekazanego zasobu, w starszych wersjach PHP poznanie identyfikatora zasobu wiązało się z jego rzutowaniem na typ int
Inne zmiany PHP 8
Do innych zmian jakie wprowadza PHP 8 należą:
- nowy typ mixed – umożliwia przypisanie wielu typów do zmiennej lub umożliwia funkcji zwracanie danych dla wielu typów (osobiście wydaje mi się, że wprowadzenie tego typu jest niepotrzebne i nie podobnie jak w przypadku typów łączonych, nie powinno być często wykorzystywane)
- nowy zwracany typ static
- zmiany w sposobie dziedziczenia metod prywatnych
- możliwość pozostawienia końcowego przecinka na liście parametrów funkcji
- implementacja obiektu token_get_all() – klasa PhpToken
- operator @ nie wycisza już błędów krytycznych (jeżeli o mnie chodzi to operator ten mógłby być całkowicie usunięty)
- możliwość użycia ::class na obiekcie
- spójne błędy typu
- zmiany ostrzeżeń silnika PHP
- zmiana w domyślnym poziomie raportowania błędów
- poprawki w pierwszeństwie konkatenacji – operator „+” ma pierwszeństwo nad operatorem „.”
- bardziej rygorystyczne sprawdzanie typów dla operatorów arytmetycznych i bitowych
- nazwy będące częściami przestrzeni nazw będą pojedynczym tokenem – oznacza to że nazwy zastrzeżone mogą już być używane jako części przestrzeni nazw
- bardziej stabilne funkcje sortowania
Opisane nowości to oczywiście nie wszystko co wprowadza nowa wersja języka PHP. Zachęcam do przejrzenia dokumentacji RFC (https://wiki.php.net/rfc#php_80), w której można znaleźć opis wszystkich wprowadzonych zmian wraz z przykładami.