PHP 8.0: Az újdonságok teljes áttekintése (1/4)

4 éve írta David Grudl  

Megjelent a PHP 8.0-s verziója. Annyira tele van újdonságokkal, mint még egyetlen verzió sem korábban. Bemutatásukhoz egyenesen négy különálló cikkre volt szükség. Ebben az elsőben megnézzük, mit hoz újat a nyelv szintjén.

Mielőtt belemerülnénk a PHP-ba, tudnia kell, hogy a Nette jelenlegi verziója teljesen készen áll a nyolcasra. Sőt, ajándékként még a Nette 2.4 is megjelent, amely teljesen kompatibilis vele, így a keretrendszer szempontjából semmi sem akadályozza meg az új verzió használatának megkezdését.

Elnevezett argumentumok

És rögtön egy bombával kezdünk, amelyet bátran nevezhetünk game changernek. Újdonság, hogy a függvényeknek és metódusoknak az argumentumokat nemcsak pozicionálisan, hanem név szerint is át lehet adni. Ami teljesen nagyszerű abban az esetben, ha egy metódusnak valóban sok paramétere van:

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
	) {
		...
	}
}

Az első két argumentumot pozicionálisan adjuk át, a többit név szerint: (az elnevezetteknek mindig a pozicionálisak után kell következniük)

$response->setCookie('lang', $lang, sameSite: 'None');

// az őrült helyett
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Ennek a funkciónak a megjelenése előtt tervben volt egy új API létrehozása a Nette-ben a sütik küldésére, mivel a setCookie() paramétereinek száma valóban megnőtt, és a pozicionális írásmód átláthatatlan volt. Most már erre nincs szükség, mert az elnevezett argumentumok ebben az esetben a legpraktikusabb API-t jelentik. Az IDE súgni fogja őket, és típusellenőrzéssel rendelkeznek.

Kiválóan alkalmasak a logikai paraméterek magyarázatára is, ahol használatuk ugyan nem szükséges, de önmagában a true vagy false nem sokat mond:

// korábban
$db = $container->getService(Database::class, true);

// most
$db = $container->getService(Database::class, need: true);

A paraméternevek mostantól a nyilvános API részévé válnak. Nem lehet őket tetszőlegesen megváltoztatni, mint eddig. Emiatt a Nette is átvizsgáláson esik át, hogy minden paraméternek megfelelő elnevezése legyen.

Az elnevezett argumentumokat a variadics-szal kombinálva is lehet használni:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// az $args-ban ['b' => 2, 'c' => 3] lesz

Újdonság, hogy az $args tömb mostantól nem numerikus kulcsokat is tartalmazhat, ami egy bizonyos BC break. Ugyanez vonatkozik a call_user_func_array($func, $args) függvény viselkedésére is, ahol mostantól az $args tömb kulcsai szignifikáns szerepet játszanak. Ezzel szemben a func_*() család függvényei árnyékolva vannak az elnevezett argumentumoktól.

Az elnevezett argumentumokkal szorosan összefügg az a tény is, hogy a splat operátor ... mostantól asszociatív tömböket is kibonthat:

variadics(...['b' => 2, 'c' => 3]);

Meglepő módon ez még nem működik a tömbökön belül:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

Az elnevezett argumentumok és a variadics kombinációja lehetőséget ad arra, hogy végre legyen egy fix szintaxis például a presenter link() metódusához, amelynek mostantól az elnevezett argumentumokat ugyanúgy átadhatjuk, mint a pozicionálisakat:

// korábban
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // tömbnek kellett lennie

// most
$presenter->link('Product:detail', $id, page: 1);

A megnevezett argumentumok szintaxisa sokkal szexibb, mint a tömbök írása, ezért a Latte azonnal átvette, ahol például a {include} és a {link} címkékben használható:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Az elnevezett paraméterekhez még visszatérünk a harmadik részben az attribútumokkal kapcsolatban.

Egy kifejezés kivételt dobhat

A kivétel dobása mostantól kifejezés. Például zárójelekbe teheti és hozzáadhatja egy if feltételhez. Hmmm, ez nem hangzik túl praktikusnak. De ez már érdekesebb:

// korábban
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('érték nincs beállítva');
}
$value = $arr['value'];


// most, amikor a throw kifejezés
$value = $arr['value'] ?? throw new \InvalidArgumentException('érték nincs beállítva');

Mivel az arrow függvények eddig csak egyetlen kifejezést tartalmazhattak, ennek a funkciónak köszönhetően kivételeket dobhatnak:

// csak egyetlen kifejezés
$fn = fn() => throw new \Exception('hoppá');

Match kifejezések

A switch-case szerkezetnek két nagy hibája van:

  • nem szigorú összehasonlítást (==) használ a === helyett
  • figyelni kell, nehogy véletlenül elfelejtsük a break-et

A PHP ezért egy alternatívát kínál az új match szerkezet formájában, amely szigorú összehasonlítást használ, és ezzel szemben nem használ break-et.

Példa switch kódra:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('ok');
        break;
    case 400:
        $message = $this->formatMessage('nem található');
        break;
    case 500:
        $message = $this->formatMessage('szerverhiba');
        break;
    default:
        $message = 'ismeretlen státuszkód';
        break;
}

És ugyanez (csak szigorú összehasonlítással) a match segítségével írva:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('nem található'),
    500 => $this->formatMessage('szerverhiba'),
    default => 'ismeretlen státuszkód',
};

Figyelje meg, hogy a match nem egy vezérlési struktúra, mint a switch, hanem egy kifejezés. A példában az eredményértékét egy változóhoz rendeljük. Ugyanakkor az egyes “lehetőségek” is kifejezések, tehát nem lehet több lépést írni, mint a switch esetében.

Ha egyik opció sem egyezik meg (és nincs default klauzula), egy UnhandledMatchError kivétel dobódik.

Mellesleg, a Latte-ban is léteznek {switch}, {case} és {default} tagek. Működésük pontosan megfelel az új match-nek. Szigorú összehasonlítást használnak, nem igényelnek break-et, és a case-ben több, vesszővel elválasztott értéket is meg lehet adni.

Nullsafe operátor

Az opcionális láncolás (optional chaining) lehetővé teszi olyan kifejezés írását, amelynek kiértékelése leáll, ha null-ra ütközik. És ez az új ?-> operátornak köszönhető. Sok olyan kódot helyettesít, amely egyébként ismételten ellenőrizné a null-t:

$user?->getAddress()?->street

// kb. ezt jelenti
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Miért „kb. ezt jelenti”? Mert valójában a kifejezés kiértékelése bonyolultabban történik, és egyetlen lépés sem ismétlődik meg. Például a $user->getAddress() csak egyszer hívódik meg, tehát nem merülhet fel az a probléma, hogy a metódus először és másodszor mást ad vissza.

Ezt a friss újdonságot egy évvel ezelőtt hozta a Latte. Most magába a PHP-ba is bekerül. Nagyszerű.

Konstruktor property promóció

Szintaktikai cukorka, amely megspórolja a típus kétszeres és a változó négyszeres írását. Kár, hogy nem akkor jött, amikor még nem voltak ilyen okos IDE-ink, amelyek ma már helyettünk írják ezt 🙂

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,
	) {}
}

A Nette DI-vel működik, azonnal elkezdheti használni.

Aritmetikai és bitenkénti operátorok szigorú viselkedése

Ami egykor a dinamikus szkriptnyelveket a csúcsra repítette, idővel a leggyengébb pontjukká vált. A PHP egykor megszabadult a “magic quotes”-tól, a globális változók regisztrálásától, és most a laza viselkedést a szigorúság váltja fel. Az az idő, amikor a PHP-ban szinte bármilyen adattípust összeadhattunk, szorozhattunk stb., amelyeknek semmi értelme nem volt, már rég elmúlt. A 7.0-s verziótól kezdve a PHP egyre szigorúbb, és a 8.0-s verziótól kezdve már bármilyen aritmetikai/bitenkénti operátor használatának kísérlete tömbökön, objektumokon vagy erőforrásokon TypeError-t eredményez. Kivétel a tömbök összeadása.

// aritmetikai és bitenkénti operátorok
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Ésszerűbb string és szám összehasonlítások

Avagy make loose operator great again.

Úgy tűnhet, hogy a laza == operátornak már nincs helye, hogy csak egy elírás a === írásakor, de ez a változás újra a térképre helyezi. Ha már megvan, viselkedjen ésszerűen. A korábbi “ésszerűtlen” összehasonlítás következménye volt például az in_array() viselkedése, amely kellemetlenül megviccelhetett:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// meglepő módon true-t adott vissza
// PHP 8.0-tól false-t ad vissza

Az == viselkedésének változása a számok és a “numerikus” stringek összehasonlítására vonatkozik, és a következő táblázat mutatja:

Összehasonlítás Korábban 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

Meglepő, hogy ez egy BC break a nyelv alapjaiban, amelyet minden ellenállás nélkül jóváhagytak. És ez jó. Itt a JavaScript sokat irigykedhetne.

Hibajelentés

Sok belső függvény mostantól TypeError-t és ValueError-t vált ki a figyelmeztetések helyett, amelyeket könnyű volt figyelmen kívül hagyni. Számos mag figyelmeztetést újraosztályoztak. A shutup operátor @ mostantól nem némítja el a fatális hibákat. És a PDO alapértelmezett módban kivételeket dob.

Ezeket a dolgokat a Nette mindig is megpróbálta valamilyen módon kezelni. A Tracy módosította a shutup operátor viselkedését, a Database átkapcsolta a PDO viselkedését, a Utils tartalmazza a standard függvények helyettesítőit, amelyek kivételeket dobnak a feltűnésmentes figyelmeztetések helyett stb. Jó látni, hogy a szigorú irány, amely a Nette DNS-ében van, a nyelv natív irányává válik.

Negatív indexű tömbök

$arr[-5] = 'első';
$arr[] = 'második';

Mi lesz a második elem kulcsa? Korábban 0 volt, PHP 8-tól -4.

Végződő vessző

Az utolsó hely, ahol nem lehetett végződő vessző, a függvényparaméterek definíciója volt. Ez már a múlté:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // végződő vessző
	) {
		....
	}

$object::class

A mágikus ::class konstans objektumokkal is működik ($object::class), ezzel teljesen helyettesítve a get_class() függvényt.

catch változó nélkül

És végül: a catch klauzulában nem szükséges megadni a kivétel változóját:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // nincs $e
	$logger->log('....');
}

A következő részekben az adattípusok terén történt alapvető újdonságokkal foglalkozunk, megmutatjuk, mik azok az attribútumok, milyen új függvények és osztályok jelentek meg a PHP-ban, és bemutatjuk a Just in Time Compilert.