PHP 8.2 ma by─ç wydane pod koniec 2022 roku, konkretna data zostanie og┼éoszona w bli┼╝ej nieokre┼Ťlonej przysz┼éo┼Ťci. W tym po┼Ťcie przyjrz─Ö si─Ö wszystkim poprawkom i wprowadzonym oraz wycofanym funkcjonalno┼Ťciom. Postaram si─Ö utrzymywa─ç ten artyku┼é na bie┼╝─ůco wraz z kolejnymi RFC, zaakceptowanymi ju┼╝ po publikacji.

System typ├│w w PHP nie jest idealny, ale jest sukcesywnie wzbogacany i rozwijany. Tym razem zmiana jest typow─ů ewolucj─ů ju┼╝ istniej─ůcych typ├│w, czy raczej sposob├│w na typowanie ÔÇö b─Ödzie mo┼╝na ┼é─ůczy─ç ze sob─ů typy unijne (logiczne LUB, czyli |) oraz typy krzy┼╝owe (logiczne I, czyli &), dzi─Öki czemu mo┼╝liwe b─Ödzie bardzo szczeg├│┼éowe, ale jednocze┼Ťnie mocno elastyczne zamodelowanie oczekiwanych/zwracanych typ├│w.

Ci─Ö┼╝ko tu m├│wi─ç o jednoznacznym zysku, poniewa┼╝ ta zmiana niesie ze sob─ů niesko┼äczon─ů ilo┼Ť─ç zastosowa┼ä. Jednocze┼Ťnie niesie sporo wyzwa┼ä dla tw├│rc├│w narz─Ödzi operuj─ůcych na Abstract Syntax Tree, takich jak #PHPStan czy #Rector - wszystkie maj─ů przed sob─ů trudne zadanie dostosowania si─Ö do zmian w sk┼éadni (cho─ç du┼╝a cz─Ö┼Ť─ç zostanie oczywi┼Ťcie wykonana w parserze) oraz dodania regu┼é weryfikuj─ůcych poprawno┼Ť─ç kodu czy te┼╝ umo┼╝liwiaj─ůcych jego refaktoryzacj─Ö.

Warto podkre┼Ťli─ç, ┼╝e RFC wnosi typy DNF, kt├│rych sk┼éadnia jest jasno okre┼Ťlona: to zbi├│r LUB, w kt├│rym poszczeg├│lne elementy mog─ů przyjmowa─ç posta─ç zbioru I. Czyli poprawny typ to A|(B&C), podczas gdy zapis A&(B|C) nie jest poprawny i wywo┼éa b┼é─ůd parsowania.

Prawdopodobnie ka┼╝dy z nas, w─Ödruj─ůc sobie po Internecie, natkn─ů┼é si─Ö na b┼é─ůd po┼é─ůczenia do bazy w stylu:

PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/html/test.php:3
Stack trace:
#0 /var/www/html/test.php(3): PDO->__construct('mysql:host=loca...', 'root', 'password')
#1 {main}

Przy odrobinie szcz─Ö┼Ťcia (lub rozumu), w┼éa┼Ťciciel strony u┼╝y┼é na tyle d┼éugiego has┼éa, ┼╝e w wy┼Ťwietlonym b┼é─Ödzie by┼éo ono przyci─Öte i nie otwiera┼éo dost─Öpu do bazy ka┼╝demu odwiedzaj─ůcemu (no, przynajmniej utrudnia┼éo). Problem nie dotyczy oczywi┼Ťcie tylko baz danych oraz b┼é─Öd├│w wy┼Ťwietlanych jawnie u┼╝ytkownikowi ÔÇö proponowany atrybut ukrywa warto┼Ťci w stack trace‘ach, a te r├│wnie┼╝ mog─ů by─ç przesy┼éane do zewn─Ötrznych us┼éug, np. #Sentry, kt├│re r├│wnie┼╝ potrafi anonimizowa─ç r├│┼╝ne dane, ale po pierwsze musi by─ç odpowiednio skonfigurowane, a po drugie w tym przypadku raczej ten mechanizm si─Ö po prostu nie sprawdzi. Dobrze zatem mie─ç mo┼╝liwo┼Ť─ç zadbania o ukrycie wra┼╝liwych danych jeszcze po stronie aplikacji.

Nowy atrybut dzia┼éa nast─Öpuj─ůco:

<?php

function test(
    $foo,
    #[\SensitiveParameter] $bar,
    $baz
) {
    throw new \Exception('Error');
}

test('foo', 'bar', 'baz');

/*
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
  thrown in test.php on line 8
*/

Uwa┼╝am, ┼╝e to przydatna funkcjonalno┼Ť─ç, kt├│ra mo┼╝e podnie┼Ť─ç #bezpiecze┼ästwo aplikacji. Nie do ko┼äca podoba mi si─Ö spos├│b prezentowania Object(SensitiveParameterValue), dlatego, ┼╝e taka forma stosowana jest r├│wnie┼╝ dla skalarnych warto┼Ťci, co wprowadza pewn─ů rozbie┼╝no┼Ť─ç mi─Ödzy faktycznym typem a tym wy┼Ťwietlanym w stack trace. Wola┼ébym co┼Ť w rodzaju SensitiveParameter<string>, SensitiveParameter<Foo> itd., by nie traci─ç istotnych informacji o przekazanym parametrze. Jest to natomiast detal, kt├│ry nie obni┼╝a przydatno┼Ťci samego atrybutu.

Bardzo si─Ö ciesz─Ö, ┼╝e PHP idzie za ciosem i wprowadza mo┼╝liwo┼Ť─ç u┼╝ycia readonly na poziomie klasy. Dzi─Öki temu modelowanie obiekt├│w przeznaczonych do transferowania danych (DTO) b─Ödzie jeszcze prostsze. W PHP 8.1 u┼╝ycie by┼éo nast─Öpuj─ůce:

class Foo
{
    public function __construct(
        readonly public string $foo,
        readonly public string $bar,
        readonly public string $baz
    ) {}
}

Od wersji 8.2, aby osi─ůgn─ů─ç ca┼ékowit─ů niemutowalno┼Ť─ç obiektu b─Ödzie trzeba u┼╝y─ç readonly tylko raz:

readonly class Foo
{
    public function __construct(
        public string $foo,
        public string $bar,
        public string $baz
    ) {}
}

Nie ma tutaj najmniejszych w─ůtpliwo┼Ťci ÔÇö to ┼Ťwietny dodatek, kt├│ry na pewno znajdzie szerokie zastosowanie w standardowej bibliotece PHP oraz projektach open source. Warto si─Ö oczywi┼Ťcie zapozna─ç z pe┼énym RFC, a zw┼éaszcza z sekcj─ů opisuj─ůc─ů ograniczenia, by wiedzie─ç jakiego rodzaju u┼╝ycie readonly jest niedozwolone.

Ciekaw─ů perspektyw─Ö przedstawi┼é Frank de Jonge:

Chodzi o to, ┼╝e readonly uniemo┼╝liwia zmian─Ö warto┼Ťci w┼éa┼Ťciwo┼Ťci po ich zainicjowaniu, a zatem du┼╝o ci─Ö┼╝ej jest tworzy─ç p┼éynne API zwracaj─ůce niemutowalne obiekty utworzone na podstawie innych niemutowalnych obiekt├│w (np. (new \DateTimeImmutable())->modify('+1 day')) w oparciu o pola tylko do odczytu. Osobi┼Ťcie jednak uwa┼╝am, ┼╝e zwyczajnie nie do tego przeznaczone jest readonly - doskonale nadaje si─Ö do tworzenia prostych, niemutowalnych struktur danych (DTO), a do obiekt├│w zawieraj─ůcych logik─Ö i API nale┼╝y po prostu u┼╝y─ç prywatnych w┼éa┼Ťciwo┼Ťci i metod (modyfikator├│w i tzw. getter├│w).

Kto pracowa┼é z systemami klasy legacy (lub po prostu w zespo┼éach, w kt├│rych jako┼Ť─ç kodu nie by┼éa priorytetem…) ten wie, ┼╝e dynamicznie zdefiniowane w┼éa┼Ťciwo┼Ťci obiektu to prawdziwa zmora. Jasne, nowoczesne IDE potrafi─ů nas ostrzec i wskaza─ç takie miejsca, ale bez wsparcia narz─Ödzi trudno ┼Ťledzi si─Ö tego typu w┼éa┼Ťciwo┼Ťci. Potrafi─ů one wprowadza─ç niez┼ée zamieszanie, gdy┼╝ patrz─ůc na kod, w kt├│rym jakie┼Ť w┼éa┼Ťciwo┼Ťci s─ů u┼╝ywane oraz na definicj─Ö klasy mo┼╝emy odnie┼Ť─ç wra┼╝enie, ┼╝e kod odwo┼éuje si─Ö do nieistniej─ůcych danych, a one po prostu zosta┼éy gdzie┼Ť dynamicznie zdeklarowane. Z drugiej strony fakt, ┼╝e PHP niejawnie tworzy nowe w┼éa┼Ťciwo┼Ťci, sprawia, ┼╝e nie jeste┼Ťmy chronieni przed zwyk┼éymi liter├│wkami czy pomy┼ékami, kt├│re jak wiemy zdarzaj─ů si─Ö nawet najlepszym ­čśë

Pozwolę sobie zademonstrować przykład wprost z RFC:

class User {
    public $name;
}

$user = new User();

// Assigns declared property User::$name.
$user->name = 'foo';

// Oops, a typo:
$user->nane = 'foo';

Tego typu dynamiczne tworzenie w┼éa┼Ťciwo┼Ťci a┼╝ do wersji 8.1 w┼é─ůcznie po prostu tworzy┼éo now─ů w┼éa┼Ťciwo┼Ť─ç, co mog┼éo powodowa─ç szereg problem├│w (zw┼éaszcza w projektach niekorzystaj─ůcych z dobrodziejstw #statycznej analizy). Od wersji 8.2 emitowane b─Ödzie ostrze┼╝enie E_DEPRECATED, a w wersji 9.0 tego rodzaju kod wywo┼éa wyj─ůtek typu Error.

Jednocze┼Ťnie ten RFC wprowadza nowy atrybut #[AllowDynamicProperties], kt├│ry umo┼╝liwia utrzymanie dotychczasowego zachowania (brak ostrze┼╝enia w 8.2 oraz wyj─ůtku w PHP9), dzi─Öki czemu mo┼╝liwe b─Ödzie migrowanie istniej─ůcych projekt├│w do nowszych wersji PHP bez konieczno┼Ťci znacznego modyfikowania kodu. Warto jednak zwr├│ci─ç uwag─Ö na to, ┼╝e nie b─Ödzie mo┼╝liwe u┼╝ycie atrybutu #[AllowDynamicProperties] w klasach oznaczonych jako readonly - takie po┼é─ůczenie wywo┼éa wyj─ůtek.

Tego rodzaju usprawnienia na poziomie j─Özyka poprawiaj─ů jako┼Ť─ç kodu i pomagaj─ů wypracowywa─ç dobre wzorce. Mog─Ö tylko przyklasn─ů─ç ­čĹĆ

Obecnie w PHP istnieje meta-typ callable, kt├│ry mo┼╝na u┼╝ywa─ç do typowania argument├│w oraz zwrotek z funkcji. Niestety, nie wszystkie dane akceptowane przez typ callable faktycznie mo┼╝na wywo┼éa─ç jako $callable(), co wprowadza du┼╝─ů niesp├│jno┼Ť─ç i stwarza ryzyko wyst─ůpienia powa┼╝nych b┼é─Öd├│w. Celem tego RFC jest wycofania wsparcia dla tych callable, kt├│re w rzeczywisto┼Ťci nie mog─ů zosta─ç wywo┼éane ÔÇö od PHP 8.2 funkcje wykorzystuj─ůce callable (np. call_user_func()) b─Öd─ů emitowa─ç ostrze┼╝enie E_DEPRECATED z informacj─ů o wycofanym u┼╝yciu. Docelowo, w wersji 9.0, takie wsparcie zostanie ca┼ékowicie usuni─Öte i typ callable nie b─Ödzie akceptowa┼é nast─Öpuj─ůcych format├│w:

"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

Wi─Ökszo┼Ť─ç z nich ma swoje zamienniki (szczeg├│┼éy w RFC), podejrzewam r├│wnie┼╝, ┼╝e wcze┼Ťniej czy p├│┼║niej pojawi─ů si─Ö regu┼éy w #Rectorze, kt├│re umo┼╝liwi─ů automatyczn─ů refaktoryzacj─Ö kodu.

Ciesz─Ö si─Ö, ┼╝e PHP sprz─ůta tego typu rzeczy, poniewa┼╝ ka┼╝da taka zmiana podnosi jako┼Ť─ç j─Özyka i zwi─Öksza jego stabilno┼Ť─ç z perspektywy dewelopera. Wi─Öcej zabezpiecze┼ä na warstwie j─Özyka to te┼╝ mniej potrzeb po stronie narz─Ödzi takich jak #PHPStan.

Celem tego RFC jest wczesne ostrze┼╝enie ludzi, ┼╝e ich kod w PHP 9 przestanie dzia┼éa─ç. Jako, ┼╝e niekt├│re formaty callable zosta┼éy porzucone ju┼╝ we wcze┼Ťniejszym RFC, ale niekt├│re specyficzne ┼Ťcie┼╝ki u┼╝ycia nie zosta┼éy wzi─Öte pod uwag─Ö w kontek┼Ťcie generowania b┼é─Öd├│w E_DEPRECATED, mog┼éo to doprowadzi─ç do sytuacji, w kt├│rej kod dzia┼éaj─ůcy poprawnie na PHP 8.2 i nie generuj─ůcy ┼╝adnych ostrze┼╝e┼ä o porzuconych formatach, m├│g┼é przesta─ç dzia┼éa─ç w PHP 9.0.

Ciesz─Ö si─Ö, ┼╝e tw├│rcy PHP dbaj─ů o u┼╝ytkownik├│w j─Özyka i zdecydowali si─Ö zmieni─ç wcze┼Ťniejsz─ů decyzj─Ö oraz wprowadzi─ç ostrze┼╝enia w tych miejscach. Wizerunkowo to na pewno dobra decyzja, poniewa┼╝ wszystkie potencjalne problemy po wydaniu wersji 9.0 ┼║le wp┼éyn─Ö┼éyby na postrzeganie PHP.

Ta zmiana powinna zainteresowa─ç wszystkich, kt├│rzy w swoich systemach stosuj─ů wieloj─Özyczno┼Ť─ç i wykonuj─ů operacje na ci─ůgach znak├│w zawieraj─ůcych znaki spora standardowego zakresu A-Z - w PHP 8.2 funkcje operuj─ůce na stringach b─Öd─ů poprawnie obs┼éugiwa─ç znaki diakrytyczne. Oznacza to, ┼╝e strtoupper('─ů') w ko┼äcu zwr├│ci ─ä, a nie ─ů jak do tej pory. Funkcje zwi─ůzane z sortowaniem r├│wnie┼╝ zaczn─ů zachowywa─ç si─Ö zgodnie z oczekiwaniem.

Jak doskonale wiemy, PHP ci─ůgnie za sob─ů d┼éuuuuugi ogon kompatybilno┼Ťci wstecznej, co bywa powodem ┼╝art├│w i w pewnym sensie daje z┼éy obraz j─Özyka. W ka┼╝dym razie wiele wbudowanych w PHP funkcji historycznie posiada sygnatury, kt├│re nie przystoj─ů nowoczesnym technologiom. Przyk┼éadowo strpos() zwraca warto┼Ť─ç liczbow─ů wskazuj─ůc─ů miejsce wyst─ůpienia wskazanego ci─ůgu znak├│w LUB w┼éa┼Ťnie false, gdy wskazany ci─ůg znak├│w nie wyst─Öpuje w innym ci─ůgu znak├│w. Problem z t─ů sygnatur─ů jest taki, ┼╝e a┼╝ do wersji 8.0 nie da┼éo si─Ö jej opisa─ç inaczej ni┼╝ poprzez @return int|false w phpDoc, co nie mia┼éo tak naprawd─Ö du┼╝ej warto┼Ťci. PHP8 umo┼╝liwi┼éo stosowanie false w tzw. union type, czyli sygnatury w stylu strpos(/* ... */): int|false s─ů w pe┼éni poprawne. Ten RFC idzie krok dalej i umo┼╝liwia stosowanie false oraz null jako samodzielnych typ├│w.

Na pierwszy rzut oka ta zmiana jest totalnie zb─Ödna (kto by chcia┼é u┼╝ywa─ç false jako zwracanego typu ­čĄö?). Jednak czytaj─ůc RFC, mo┼╝emy zauwa┼╝y─ç przyk┼éady opisuj─ůce kowariancj─Ö i kontrawariancj─Ö, i wtedy ta zmiana nabiera sensu (niewielkiego, ale jednak). Sp├│jrzmy na przyk┼éad:

class User {}

interface UserFinder
{
    function findUserByEmail(): User|null;
}

class AlwaysNullUserFinder implements UserFinder
{
    function findUserByEmail(): null
    {
        return null;
    }
}

Dzi─Öki zmianie z tego RFC mo┼╝liwe jest zaimplementowanie interfejsu i zachowanie zgodno┼Ťci z LSP ograniczaj─ůc oryginaln─ů sygnatur─Ö zwracanego typu User|null do samego null. Nie jest to w moim odczuciu co┼Ť, co znajdzie szerokie zastosowanie ÔÇö osobi┼Ťcie widz─Ö tu pole do popisu w testach czy ┼Ťrodowiskach deweloperskich, gdzie pewne cz─Ö┼Ťci systemu b─Öd─ů po prostu podmieniane na implementacje-wydmuszki. Cho─ç mo┼╝e po prostu nie napotka┼éem jeszcze scenariuszy, gdzie ta zmiana rozwi─ůza┼éaby realny problem ­čśë

Niewiele mo┼╝na na ten temat napisa─ç, po prostu w PHP 8.2 true stanie si─Ö samodzielnym typem, tak jak wcze┼Ťniej zosta┼éy nim false oraz null. Podobnie jak w kontek┼Ťcie poprzedniej zmiany, tak i tutaj osobi┼Ťcie nie widz─Ö realnego zastosowania. Podejrzewam, ┼╝e ma to znaczenie w standardowej bibliotece PHP oraz mo┼╝e mie─ç zastosowanie w specyficznych bibliotekach open source. Natomiast nawet je┼Ťli ja nie widz─Ö zastosowania, to nie znaczy, ┼╝e ta zmiana nie jest potrzebna ÔÇö dobrze jest mie─ç wi─Öksze pole manewru.

Obecnie PHP ma cztery sposoby na dynamiczne wstrzykiwanie warto┼Ťci do ci─ůg├│w znak├│w: "$foo", "{$foo}", "${foo}" oraz "${$foo}" (ostatni przypadek to tzw. variable variable, czyli dynamiczne odwo┼éanie do zmiennej poprzez warto┼Ť─ç przypisan─ů do innej zmiennej ÔÇö sk┼éadnia do┼Ť─ç niszowa i raczej rzadko stosowana). Ten RFC wycofuje z u┼╝ycia dwie ostatnie metody, a wsparcie dla nich zostanie ca┼ékowicie usuni─Öte w PHP9.

Przyczyn─ů wycofania interpolacji ${} jest fakt, ┼╝e obie mia┼éy praktycznie identyczn─ů sk┼éadni─Ö, a jednocze┼Ťnie zupe┼énie odmienne dzia┼éanie. Wprawdzie wydaje si─Ö, ┼╝e tego typu konflikty to brzegowe przypadki, mimo to dobrze, ┼╝e PHP robi w tej kwestii porz─ůdek i zmniejsza ilo┼Ť─ç wspieranych sk┼éadni interpolacji. W przysz┼éo┼Ťci mo┼╝e zostan─ů dodane nowe rodzaje interpolacji (jak np. sugerowane w RFC "{$:func()}"), ale zanim to nast─ůpi, dobrze by by┼éo ujednolici─ç to, co ju┼╝ jest dost─Öpne w j─Özyku.

Ta zmiana jest na tyle niskopoziomowa, ┼╝e wi─Ökszo┼Ť─ç u┼╝ytkownik├│w PHP nigdy nie odczuje r├│┼╝nicy. Jest jednak istotny aspekt tej zmiany powoduj─ůcy niekompatybilno┼Ť─ç wsteczn─ů - mysqlnd nie wspiera i nie b─Ödzie wspiera┼é automatycznego ponawiania po┼é─ůcze┼ä. Biblioteki czy systemy, kt├│re korzysta┼éy z tej funkcjonalno┼Ťci, b─Öd─ů musia┼éy znale┼║─ç inne sposoby na osi─ůgni─Öcie takiego zachowania.

Motywacj─ů tego RFC jest fakt, ┼╝e wspomniane funkcje nie s─ů wystarczaj─ůco dobrze okre┼Ťlone. Maj─ů one ograniczone dzia┼éanie, podczas gdy ich nazwa mo┼╝e sugerowa─ç, ┼╝e s─ů bardziej uniwersalne. Generalnie format├│w kodowania jest du┼╝o i jest to skomplikowany temat, wi─Öc ludzie cz─Östo pope┼éniaj─ů b┼é─Ödy korzystaj─ůc z tych funkcji. Zostan─ů one zatem oznaczone jako wycofane w PHP 8.2, a nast─Öpnie usuni─Öte w PHP 9.0.