PHP 8.0: Nowe funkcje, klasy i JIT (4/4)
Ukazała się wersja PHP 8.0. Jest tak naładowana nowościami, jak żadna wersja wcześniej. Ich przedstawienie wymagało aż czterech oddzielnych artykułów. W tym ostatnim przyjrzymy się nowym funkcjom i klasom oraz przedstawimy Just in Time Compiler.

Nowe funkcje
Standardowa biblioteka PHP dysponuje setkami funkcji, a w wersji 8.0 pojawiło się sześć nowych. Wygląda to na mało, ale większość z nich łata słabe punkty języka. Co ładnie koresponduje z wydźwiękiem całej wersji 8.0, która dopracowuje i konsoliduje PHP jak żadna wersja wcześniej. Przegląd wszystkich nowych funkcji i metod znajdziesz w przewodniku migracji.
str_contains()
str_starts_with()
str_ends_with()
Funkcje do sprawdzania, czy ciąg znaków zaczyna się, kończy lub zawiera podciąg.
if (str_contains('Nette', 'te')) {
...
}
Wraz z pojawieniem się tej trójki PHP definiuje, jak postępować z pustym ciągiem znaków podczas wyszukiwania, według czego kierują się również wszystkie inne powiązane funkcje, a mianowicie tak, że pusty ciąg znaków znajduje się wszędzie:
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (wcześniej false)
Dzięki temu zachowanie trójki jest całkowicie identyczne z odpowiednikami w Nette:
str_contains() # Nette\Utils\String::contains()
str_starts_with() # Nette\Utils\String::startsWith()
str_ends_with() # Nette\Utils\String::endsWith()
Dlaczego te funkcje są tak ważne? Standardowe biblioteki wszystkich
języków są zawsze obciążone historycznym rozwojem i nie da się uniknąć
powstawania niespójności i potknięć. Ale jednocześnie jest to wizytówka
danego języka. Dziwne jest, gdy w 25-letnim PHP brakuje funkcji do tak
podstawowych operacji, jak zwracanie pierwszego czy ostatniego elementu
z tablicy, escapowanie HTML bez pułapek (htmlspecialchars
nie
escapuje apostrofu), czy właśnie wyszukiwanie ciągu znaków w ciągu znaków.
To, że da się to jakoś obejść, nie wytrzymuje krytyki, ponieważ
wynikiem nie jest czytelny i zrozumiały kod. Jest to nauczka dla wszystkich
autorów API. Kiedy widzisz, że znaczną część dokumentacji funkcji zajmuje
wyjaśnienie pułapek (jak na przykład wartości zwracane przez
strpos
), jest to jasny sygnał do modyfikacji biblioteki i dodania
właśnie str_contains
.
get_debug_type()
Zastępuje już przestarzałe get_type()
. Zamiast długich
typów jak integer
zwraca dzisiaj używane int
, w
przypadku obiektów zwraca od razu typ:
Wartość | gettype() | get_debug_type() |
---|---|---|
'abc' |
string |
string |
[1, 2] |
array |
array |
231 |
integer |
int |
3.14 |
double |
float |
true |
boolean |
bool |
null |
NULL |
null |
new stdClass |
object |
stdClass |
new Foo\Bar |
object |
Foo\Bar |
function() {} |
object |
Closure |
new class {} |
object |
class@anonymous |
new class extends Foo {} |
object |
Foo@anonymous |
curl_init() |
resource |
resource (curl) |
curl_close($ch) |
resource (closed) |
resource (closed) |
Obiektywizacja zasobów
Wartości typu resource pochodzą z czasów, gdy PHP jeszcze nie miało obiektów, ale właściwie ich potrzebowało. Tak powstały zasoby. Dzisiaj mamy obiekty i w porównaniu do zasobów działają znacznie lepiej z garbage collectorem, więc w planach jest stopniowe zastąpienie ich wszystkich obiektami.
Od PHP 8.0 na obiekty zmieniają się zasoby obrazów, połączeń curl, openssl, xml, itp.. W PHP 8.1 przyjdzie kolej na połączenia FTP itd.
$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage // true
is_resource($res) // false - BC break
Te obiekty na razie nie mają żadnych metod, ani nie można bezpośrednio
tworzyć ich instancji. Chodzi na razie rzeczywiście tylko o to, aby pozbyć
się z PHP przestarzałych zasobów bez zmiany API. I to dobrze, ponieważ
stworzenie dobrego API jest oddzielnym i wymagającym zadaniem. Nikt nie chce,
aby w PHP powstały kolejne klasy jak SplFileObject z metodami nazwanymi
fgetc()
lub fgets()
.
PhpToken
Do obiektów przenosi się również tokenizer, a więc funkcje wokół
token_get_all
. Tym razem nie chodzi o pozbywanie się zasobów,
ale otrzymujemy pełnoprawny obiekt reprezentujący jeden token PHP.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // instancja PhpToken
echo $token->id; // T_OPEN_TAG
echo $token->text; // '<?php'
echo $token->line; // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING); // false
echo $token->isIgnorable(); // true
Metoda isIgnorable()
zwraca true dla tokenów
T_WHITESPACE
, T_COMMENT
, T_DOC_COMMENT
i
T_OPEN_TAG
.
Weak maps
Weak mapy są związane z gargabe kolektorem, który zwalnia z pamięci wszystkie obiekty i wartości, które nie są już używane (tj. nie ma żadnej używanej zmiennej ani właściwości, która by je zawierała). Ponieważ życie wątku PHP jest efemeryczne, a pamięci mamy dzisiaj na serwerach pod dostatkiem, kwestie związane z efektywnym zwalnianiem pamięci zazwyczaj w ogóle nas nie dotyczą. Ale w przypadku długo działających skryptów są kluczowe.
Obiekt WeakMap
jest podobny do SplObjectStorage
. W
obu jako klucze używane są obiekty i umożliwiają przechowywanie pod nimi
dowolnych wartości. Różnica polega na tym, że WeakMap
nie
zapobiega zwolnieniu obiektu przez garbage kolektor. Tj. jeśli jedynym
miejscem, gdzie obiekt jeszcze występuje, jest klucz w weak mapie, zostanie on
usunięty z mapy i pamięci.
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 'dane dla $obj';
dump(count($map)); // 1
unset($obj);
dump(count($map)); // 0
Do czego to jest dobre? Na przykład do kešowania. Mamy metodę
loadComments()
, której przekazujemy artykuł na blogu, a ona
zwraca wszystkie jego komentarze. Ponieważ metoda jest wywoływana z tym samym
artykułem wielokrotnie, stworzymy sobie jeszcze getComments()
,
która będzie kešować wynik pierwszej metody:
class Comments
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap;
}
public function getComments(Article $article): ?array
{
$this->cache[$article] ??= $this->loadComments($article);
return $this->cache[$article]
}
...
}
Sztuczka polega na tym, że w momencie, gdy obiekt $article
zostanie zwolniony (np. aplikacja zacznie pracować z innym artykułem),
zwolniona zostanie również jego pozycja z cache.
PHP JIT (Just in Time Compiler)
Być może wiesz, że PHP kompiluje się do tzw. opcode, czyli instrukcji niskiego poziomu, które można zobaczyć na przykład tutaj, a które wykonuje maszyna wirtualna PHP. A co to jest JIT? JIT potrafi transparentnie kompilować PHP bezpośrednio do kodu maszynowego, który wykonuje bezpośrednio procesor, dzięki czemu omija się wolniejsze wykonywanie przez maszynę wirtualną.
JIT ma więc przyspieszyć PHP.
Próby implementacji JIT w PHP sięgają aż 2011 roku i stoi za nimi Dmitry Stogov. Od tego czasu wypróbował 3 różne implementacje, ale żadna z nich nie trafiła do oficjalnego PHP z tych powodów: wynikiem nigdy nie był żaden znaczący wzrost wydajności dla typowych aplikacji webowych; komplikuje utrzymanie PHP (tj. nikt oprócz Dmitry'ego tego nie rozumie 😉); istniały inne sposoby na poprawę wydajności, bez konieczności używania JIT.
Skokowy wzrost wydajności PHP w wersji 7 był produktem ubocznym pracy nad JIT, chociaż paradoksalnie do jego wdrożenia nie doszło. Dochodzi do tego dopiero w PHP 8. Ale od razu będę hamować przesadne oczekiwania: prawdopodobnie żadnego przyspieszenia nie zauważysz.
Dlaczego więc JIT wchodzi do PHP? Po pierwsze, inne sposoby na poprawę wydajności powoli się wyczerpują, a JIT jest po prostu następny w kolejce. W zwykłych aplikacjach webowych wprawdzie przyspieszenia nie przynosi, ale zasadniczo przyspiesza na przykład obliczenia matematyczne. Otwiera się więc możliwość zaczęcia pisania tych rzeczy w PHP. A właściwie można by w PHP implementować funkcje, które dotychczas wymagały implementacji bezpośrednio w C ze względu na szybkość.
JIT jest częścią rozszerzenia opcache
i włącza się go
razem z nim w php.ini (przeczytaj dokumentację
dotyczącą tej czwórki cyfr):
zend_extension=php_opcache.dll
opcache.jit=1205 ; konfiguracja za pomocą czwórki OTRC
opcache.enable_cli=1 ; aby działało również w CLI
opcache.jit_buffer_size=128M ; zarezerwowana pamięć dla skompilowanego kodu
Że JIT działa, dowiesz się na przykład w panelu informacyjnym w Tracy Baru.
JIT bardzo dobrze działa wtedy, gdy wszystkie zmienne mają jasno określone
typy i nie mogą się zmieniać przy wielokrotnym wywołaniu tego samego kodu.
Jestem więc ciekaw, czy kiedyś będziemy w PHP deklarować typy również dla
zmiennych: string $s = 'Witaj, to jest zakończenie serii';
Aby przesłać komentarz, proszę się zalogować