PHP 8.0: Kompletní přehled novinek (1/4)
Vyšlo PHP verze 8.0. Je tak nadupané novinkami, jako nebyla žádná verze předtím. Jejich představení si vyžádalo rovnou čtyři samostatné články. V tomhle prvním se podíváme, co nového přináší po jazykové stránce.
Ještě než se ponoříme do PHP, vězte, že aktuální verze Nette je pro osmičku plně připravena. Ba co víc, jako dárek dokonce vyšlo i Nette 2.4, které je s ní plně kompatibilní, takže z pohledu frameworku vám nic nebrání novou verzi začít používat.
Pojmenované argumenty
A začneme rovnou bombou, kterou lze směle označit za game changer. Nově lze předávat funkcím a metodám argumenty nejen pozičně, ale i podle jmen. Což je naprosto skvělé v případě, že metoda má parametrů opravdu hodně:
class Response implements IResponse
{
public function setCookie(
string $name,
string $value,
string|DateInterface|null $time,
string $path = null,
string $domain = null,
bool $secure = null,
bool $httpOnly = null,
string $sameSite = null
) {
...
}
}
První dva argumenty předáme pozičně, další podle jména: (pojmenované musí vždy následovat až po pozičních)
$response->setCookie('lang', $lang, sameSite: 'None');
// místo šíleného
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
Před příchodem této featury bylo v plánu vytvořit v Nette nové API
pro odesílání cookies, protože počet parametrů setCookie()
opravdu narostl a poziční zápis byl nepřehledný. Teď už to není
potřeba, protože pojmenované argumenty jsou v tomhle případě tím
nejpraktičtějším API. IDE je budou napovídat a mají typovou kontrolu.
Skvěle se hodí i pro dovysvětlení logických parametrů, kde jejich
užití sice není nutné, ale samotné true
nebo
false
mnoho neříká:
// dříve
$db = $container->getService(Database::class, true);
// nyní
$db = $container->getService(Database::class, need: true);
Názvy parametrů se nyní stávají součástí veřejného API. Není možné je libovolně měnit jako doposud. Z toho důvodu i Nette prochází auditem, zda všechny parametry mají vhodné pojmenování.
Pojmenované argumenty lze používat také v kombinaci s variadics:
function variadics($a, ...$args) {
dump($args);
}
variadics(a: 1, b: 2, c: 3);
// v $args bude ['b' => 2, 'c' => 3]
Nově tak může pole $args
obsahovat i nenumerické klíče,
což je určitý BC break. Totéž se týká i chování funkce
call_user_func_array($func, $args)
, kde nyní hrají klíče
v poli $args
signifikantní roli. Naopak funkce z rodiny
func_*()
jsou od pojmenovaných argumentů odstíněny.
S pojmenovanými argumenty také úzce souvisí fakt, že splat operátor
...
může nyní rozbalit i asociativní pole:
variadics(...['b' => 2, 'c' => 3]);
Kupodivu to zatím nefunguje uvnitř polí:
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
Kombinace pojmenovaných argumentů a variadics dává možnost konečně
mít pevnou syntax třeba pro metodu presenteru link()
, které teď
můžeme pojmenované argumenty předávat stejně jako poziční:
// dříve
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // muselo být pole
// nyní
$presenter->link('Product:detail', $id, page: 1);
Syntax pro pojmenované parametry je mnohem více sexy, než zápis polí,
proto si ji také ihned osvojilo
Latte, kde ji lze používat například ve značkách
{include}
a {link}
:
{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}
K pojmenovaným parametrům se ještě vrátíme ve třetím díle v souvislosti s atributy.
Výraz může vyhodit výjimku
Vyhození výjimky je nyní výraz. Můžete jej třeba obalit závorkami a
přidat do podmínky if
. Hmmm, to nezní příliš prakticky. Ale
tohle už je zajímavější:
// dříve
if (!isset($arr['value'])) {
throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];
// nyní, když je throw výraz
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');
Protože arrow funkce mohou dosud obsahovat pouze jeden výraz, díky této feature mohou vyhazovat výjimky:
// only single expression
$fn = fn() => throw new \Exception('oops');
Match Expressions
Konstrukce switch-case
má dvě velké nectnosti:
- používá nestriktní porovnávání
==
místo===
- musíte dávat pozor, abyste omylem nezapomněli na
break
PHP proto přichází s alternativou v podobě nové konstrukce
match
, která používá striktní porovnávání a naopak
nepoužívá break
.
Příklad kódu switch
:
switch ($statusCode) {
case 200:
case 300:
$message = $this->formatMessage('ok');
break;
case 400:
$message = $this->formatMessage('not found');
break;
case 500:
$message = $this->formatMessage('server error');
break;
default:
$message = 'unknown status code';
break;
}
A totéž (jen se striktním porovnáváním) zapsané pomocí
match
:
$message = match ($statusCode) {
200, 300 => $this->formatMessage('ok'),
400 => $this->formatMessage('not found'),
500 => $this->formatMessage('server error'),
default => 'unknown status code',
};
Všimněte si, že match
není kontrolní struktura
jakoswitch
, ale výraz. Jeho výslednou hodnotu v příkladu
přiřazujeme do proměnné. Zároveň i jednotlivé „možnosti“ jsou
výrazy, nelze tedy zapsat více kroků, jako v případě
switch
.
Pokud nedojde ke shodě se žádnou z voleb (a neexistuje klauzule default),
vyhodí se výjimka UnhandledMatchError
.
Mimochodem, v Latte také existují značky {switch}
,
{case}
a {default}
. Jejich fungování odpovídá
přesně novému match
. Používají striktní porovnávání,
nevyžadují break
a v case
je možné uvést více
hodnot oddělených čárkami.
Nullsafe operator
Volitelné řetězení (optional chaining) umožňuje psát výraz, jehož
vyhodnocování se zastaví, pokud narazí na null. A to díky novému
operátoru ?->
. Nahradí spoustu kódu, který by jinak
opakovaně kontroloval null:
$user?->getAddress()?->street
// znamená cca
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
Proč „znamená cca“? Protože ve skutečnosti se výraz vyhodnocuje
důmyslněji a žádný krok se neopakuje. Například
$user->getAddress()
se volá jen jednou, tedy nemůže nastat
problém způsobený tím, že by metoda poprvé a podruhé vrátila něco
jiného.
Tuhle svěží vychytávku přineslo před rokem Latte. Nyní se dostává do samotného PHP. Paráda.
Constructor property promotion
Syntaktický cukr, který ušetří dvojí psaní typu a čtyřnásobné psaní proměnné. Škoda jen, že nepřišel v době, kdy jsme neměli tak chytré IDE, které to dnes píší za nás 🙂
class Facade
{
private Nette\Database\Connection $db;
private Nette\Mail\Mailer $mailer;
public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
{
$this->db = $db;
$this->mailer = $mailer;
}
}
class Facade
{
public function __construct(
private Nette\Database\Connection $db,
private Nette\Mail\Mailer $mailer,
) {}
}
S Nette DI funguje, můžete hned začít používat.
Striktní chování aritmetických a bitových operátorů
Co kdysi vystřelilo dynamické skriptovací jazyky na výsluní, se časem stalo jejich nejslabším místem. PHP se kdysi zbavovalo „magic quotes“, registrování globálních proměnných a nyní uvolněné chování střídá striktnost. Doba, kdy jste v PHP mohli sčítat, násobit atd. téměř jakékoliv datové typy, u nichž to nedávalo pražádný žádný smysl, je dávno pryč. Počínaje verzí 7.0 je PHP čím dál tím striktnější a od verze 8.0 už pokus o použití jakéhokoliv aritmetického/bitového operátoru u polí, objektů či resources končí TypeError. Výjimkou je sčítání polí.
// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Rozumnější porovnávání řetězců a čísel
Aneb make loose operator great again.
Zdálo by se, že pro loose operátor ==
již není místo, že
jde jen o překlep při psaní ===
, ale tato změna jej vrací
znovu na mapu. Když už ho máme, ať se chová rozumně. Důsledkem
dřívějšího „nerozumného“ porovnávání bylo třeba chování
in_array()
, které vás mohlo nemile vypéct:
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// překvapivě vracelo true
// od PHP 8.0 vrací false
Změna v chování ==
se týká porovnávání čísel a
„číselných“ řetězců a ukazuje ji následující tabulka:
Comparison | Before | PHP 8.0 |
---|---|---|
0 == "0" |
true | true |
0 == "0.0" |
true | true |
0 == "foo" |
true | false |
0 == "" |
true | false |
42 == " 42" |
true | true |
42 == "42 " |
true | true |
42 == "42foo" |
true | false |
42 == "abc42" |
false | false |
"42" == " 42" |
true | true |
"42" == "42 " |
false | true |
Překvapivé je, že jde o BC break v samotném základu jazyka, který byl schválen bez jakéhokoliv odporu. A to je dobře. Tady by JavaScript mohl hodně závidět.
Reportování chyb
Mnohé interní funkce nyní vyvolávají TypeError a ValueError místo
varování, které se dalo snadno opomenout. Byla reklasifikována řada
varování jádra. Shutup operátor @
nyní neztiší fatální
chyby. A PDO v defaultním režimu vyhazuje výjimky.
Tyhle věci se vždy Nette snažilo nějakým způsobem řešit. Tracy upravovala chování shutup operátoru, Database přepínalo chování PDO, Utils obsahuje náhrady standardních funkcí, které vyhazují výjimky místo nenápadných varování atd. Je fajn vidět, že striktní směr, který má v Nette ve své DNA, se stává nativním směrem jazyka.
Pole s negativní indexem
$arr[-5] = 'first';
$arr[] = 'second';
Jaký bude klíč druhého prvku? Dříve to byla 0
, od PHP
8 je to -4
.
Koncová čárka
Poslední místo, kde nemohla být koncová čárka, byla definice parametrů funkce. To už je minulostí:
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // trailing comma
) {
....
}
$object::class
Magická konstanta ::class
funguje i s objekty
$object::class
, čímž zcela nahrazuje funkci
get_class()
.
catch bez proměnné
A nakonec: v klauzuli catch není nutné uvádět proměnnou pro výjimku:
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // no $e
$logger->log('....');
}
V příštích dílech nás čekají zásadní novinky v datových typech, ukážeme si co jsou to atributy, jaké se v PHP objevily nové funkce a třídy a představíme si Just in Time Compiler.
Komentáře
Díky, konečně se toho někdo ujal a pěkně vysvětlil co vše PHP 8 mění… Těším se na další článek!
Až do teď, jsem místo podmiňování, nebo switchování používal pole.
Tvůj zápis v mém poli pro MATCH by u mě vypadal takto:
$statusMessages = [
200 ⇒ null,
300 ⇒ null,
400 ⇒ ‚not found‘,
500 ⇒ ‚server error‘,
};
$message = $statusMessages[$statusCode] ?? ‚unknown status code‘;
Match zjednodušuje tento zápis, protože nemusím používat ?? operátor, ani v poli definovat 2 klíče se stejnou hodnotou zvlášť.
Otázka ale zní:
Pole jsem si nadefinoval někde jako třídní proměnnou hodnot a poté jej používal na více místech.
Lze match nadefinovat někde bokem jako například v C parametrizované makro, nebo je nutno na to vytvořit například funkci?
Otazka k te kompatibilite… Tracy podporuje PHP8 ve verzi, ktera ma konflikt s DI<3.
Díky za skvělý článek. Našel jsem jednu drobnou chybu: 42 == "42 " je true i v PHP 8: https://3v4l.org/qq6Yb
#2 Polki ono to není z toho příkladu zřejmé, ale výhoda match je v tom, že pokud hodnotou bude nějaký výraz, nebo třeba volání funkce, zavolá se až když matchne, oproti řešení s polem.
#3 pakl Tracy 2.6.8 ti s PHP 8 nefunguje?
#4 vrana díky, opravil jsem to
#5 David Grudl Tracy 2.6.8 samozrejme funguje. Dekuju a omlouvam se. Nejak mi nedoslo, ze tracy a latte vlastne nenasleduji major verzi Nette
#6 pakl tak paráda. Už nikde starší Nette neopoužívám, tak mi mohlo samozřejmě něco uniknout.
#2 Polki Diky Davide za hezké vysvětlení.
💙
lovely tlesk tlesk
Chcete-li odeslat komentář, přihlaste se