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

5 é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.

David Grudl Programmer, blogger, and AI evangelist who created the Nette Framework powering hundreds of thousands of websites. He explores artificial intelligence on Uměligence and web development on phpFashion. Weekly, he hosts Tech Guys and teaches people to master ChatGPT and other AI tools. He's passionate about transformative technologies and excels at making them accessible to everyone.
OSZAR »