PHP 8.0: Prezentare completă a noutăților (1/4)

acum 3 ani De la David Grudl  

Versiunea 8.0 a PHP este lansată chiar acum. Este plină de noutăți ca nicio altă versiune de până acum. Introducerea lor a meritat patru articole separate. În primul vom arunca o privire asupra a ceea ce aduce la nivel de limbaj.

Înainte de a ne adânci în PHP, să știm că versiunea actuală a Nette este complet pregătită pentru cea de-a opta versiune. Mai mult, ca un cadou, a fost lansat un Nette 2.4 complet compatibil, deci din punct de vedere al framework-ului nu există nimic care să vă împiedice să îl folosiți.

Argumente numite

Să începem imediat cu o bombă, care ar putea fi desemnată cu îndrăzneală ca fiind un schimbător de joc. Argumentele pot fi acum transmise funcțiilor și metodelor nu doar pozițional, ci și în funcție de numele lor. Ceea ce este absolut mișto în cazul în care o metodă are într-adevăr prea mulți parametri:

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

Primele două argumente sunt trecute pozițional, iar celelalte prin numele lor: (cele numite trebuie să urmeze după cele poziționale).

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

// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Înainte de introducerea acestei caracteristici, existau planuri de a crea un nou API pentru trimiterea de cookie-uri în Nette, deoarece numărul de argumente pentru setCookie() a crescut foarte mult, iar notația pozițională era derutantă. Acest lucru nu mai este necesar, deoarece argumentele numite sunt în acest caz cea mai convenabilă API. IDE le va indica și există siguranță de tip.

Ele sunt ideale chiar și pentru explicarea argumentelor logice, în cazul în care utilizarea lor nu este necesară, dar un simplu true sau false nu este suficient:

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

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

Numele argumentelor fac acum parte din API-ul public. Nu mai este posibil să le modificăm după bunul plac. Din această cauză, chiar și Nette trece printr-un audit pentru a determina dacă toate argumentele au un nume adecvat.

Argumentele cu nume pot fi utilizate și în combinație cu variadicele:

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

variadics(a: 1, b: 2, c: 3);
// $args will contain ['b' => 2, 'c' => 3]

Matricea $args poate conține acum chiar și chei nenumerice, ceea ce reprezintă un fel de pauză BC. Același lucru este valabil și pentru comportamentul din call_user_func_array($func, $args), unde cheile din matricea $args joacă acum un rol mult mai important. Dimpotrivă, funcțiile din familia func_*() sunt ferite de argumente numite.

Argumentele numite sunt strâns legate de faptul că operatorul splat ... poate acum să extindă array-uri asociative:

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

În mod surprinzător, acesta nu funcționează deocamdată în interiorul tablourilor:

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

Combinația dintre argumentele numite și variadice oferă opțiunea de a avea în sfârșit o sintaxă fixă, de exemplu, pentru metoda link(), căreia îi putem transmite atât argumente numite, cât și poziționale:

// before
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // had to be an array

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

Sintaxa pentru argumente numite este mult mai sexy decât scrierea de array-uri, așa că Latte a adoptat-o imediat, unde poate fi utilizată, de exemplu, în etichetele {include} și {link}:

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

Vom reveni la argumentele numite în a treia parte a seriei, în legătură cu atributele.

O expresie poate arunca o excepție

Aruncarea unei excepții este acum o expresie. Puteți să o puneți între paranteze și să o adăugați la o condiție if. Hmmm, asta nu sună foarte practic. Totuși, acest lucru este mult mai interesant:

// before
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// now, when throw is an expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

Deoarece funcțiile săgeată nu pot conține până acum decât o singură expresie, ele pot acum să arunce excepții datorită acestei caracteristici:

// only single expression
$fn = fn() => throw new \Exception('oops');

Expresie de potrivire

Instrucțiunea switch-case are două vicii majore:

  • utilizează comparația non-strictă == în loc de ===
  • trebuie să aveți grijă să nu uitați din greșeală de break

Din această cauză, PHP vine cu o alternativă sub forma unei noi expresii match, care utilizează o comparație strictă și, invers, nu utilizează break.

switch exemplu de cod:

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;
}

Și același lucru (doar cu comparație strictă) scris folosind match:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('not found'),
    500 => $this->formatMessage('server error'),
    default => 'unknown status code',
};

Rețineți că match nu este o structură de control ca switch, ci o expresie. În exemplu, atribuim valoarea sa rezultată unei variabile. În același timp, “opțiunile” individuale sunt, de asemenea, expresii, astfel încât nu este posibilă scrierea mai multor pași, ca în cazul lui switch.

În cazul în care nu există nicio potrivire (și nu există nicio clauză implicită), se aruncă excepția UnhandledMatchError.

Apropo, există și etichete {switch}, {case} și {default} în Latte. Funcția lor corespunde exact noului match. Acestea utilizează o comparație strictă, nu necesită break și este posibilă specificarea mai multor valori separate prin virgule în case.

Operatorul Nullsafe

Încatenarea opțională vă permite să scrieți o expresie a cărei evaluare se oprește dacă întâlnește nul. Acest lucru se datorează noului operator ?->. Acesta înlocuiește o mulțime de cod care, altfel, ar trebui să verifice în mod repetat dacă este nul:

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

// approximately translates to
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

De ce “aproximativ”? Pentru că, în realitate, expresia este evaluată mai ingenios, astfel încât nu se repetă niciun pas. De exemplu, $user->getAddress() este apelat o singură dată, astfel încât nu se poate întâmpina problema cauzată de faptul că metoda returnează ceva diferit pentru prima și a doua oară.

Această caracteristică a fost adusă de Latte acum un an. Acum, PHP însuși o adoptă. Grozav.

Promovarea proprietăților constructorului

Zahăr sintactic care economisește scrierea de două ori a tipului și de patru ori a variabilei. Păcat că nu a venit într-un moment în care nu aveam IDE-uri atât de inteligente care astăzi să scrie în locul nostru 🙂

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

Funcționează cu Nette DI, puteți începe să îl folosiți.

Verificări de tip mai stricte pentru operatorii aritmetici și operatorii bitwise

Ceea ce odată a ajutat limbajele de scripting dinamice să se impună a devenit punctul lor cel mai slab. Cândva, PHP a scăpat de “ghilimelele magice”, de înregistrarea variabilelor globale, iar acum comportamentul relaxat este înlocuit de strictețe. Vremea în care în PHP puteai să adaugi, să înmulțești etc. aproape orice tip de date pentru care nu avea sens, a trecut de mult. Începând cu versiunea 7.0, PHP devine din ce în ce mai strict și, începând cu versiunea 8.0, o încercare de a utiliza orice operator aritmetic/bitwise pe array-uri, obiecte sau resurse se termină cu TypeError. Excepție fac adăugirile de array-uri.

// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Comparații mai sănătoase între șiruri și numere

Sau faceți ca operatorul loose să fie din nou grozav.

S-ar părea că nu mai este loc pentru operatorul loose ==, că este doar o greșeală de scriere la scrierea ===, dar această modificare îl readuce din nou pe hartă. Dacă îl avem deja, lăsați-l să se comporte în mod rezonabil. Ca urmare a comparației “nerezonabile” de mai devreme, de exemplu, in_array() ar putea să te trolleze neplăcut:

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

dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns false

Modificarea comportamentului din == se referă la compararea numerelor și a șirurilor “numerice” și este prezentată în tabelul următor:

Comparare Înainte 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

În mod surprinzător, este vorba de o întrerupere a BC în chiar centrul limbii, care a fost aprobată fără nicio rezistență. Și asta este bine. JavaScript ar putea fi foarte invidios în această privință.

Raportarea erorilor

Multe funcții interne declanșează acum TypeError și ValueError în loc de avertismente care erau ușor de ignorat. O serie de avertismente din kernel au fost reclasificate. Operatorul de închidere @ nu mai reduce acum la tăcere erorile fatale. Iar PDO aruncă excepții în mod implicit.

Nette a încercat întotdeauna să rezolve aceste lucruri într-un fel sau altul. Tracy a modificat comportamentul operatorului shutup, Database a schimbat comportamentul PDO, Utils conține înlocuirea funcțiilor standard, care aruncă excepții în loc de avertismente ușor de trecut cu vederea, etc. Este plăcut să vezi că direcția strictă pe care Nette o are în ADN-ul său devine direcția nativă a limbajului.

Incrementări negative ale cheilor de matrice

$arr[-5] = 'first';
$arr[] = 'second';

Care va fi cheia celui de-al doilea element? Înainte era 0, since PHP 8 it’s -4.

Virgula de sfârșit

Ultimul loc în care virgula de sfârșit nu putea fi, era definirea argumentelor funcțiilor. Acest lucru este de domeniul trecutului:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // trailing comma
	) {
		....
	}

$object::class

Constanta magică ::class funcționează, de asemenea, cu obiectele $object::class, înlocuind complet funcția get_class().

Prindeți excepțiile numai după tip

Și în cele din urmă: nu este necesar să specificați o variabilă pentru excepție în clauza catch:

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

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

*În următoarele părți, vom vedea noutățile majore în ceea ce privește tipurile de date, vom arăta ce sunt atributele, ce funcții și clase noi au apărut în PHP și vom prezenta compilatorul Just in Time.