PHP 8.0 : Aperçu complet des nouveautés (1/4)
La version 8.0 de PHP sort en ce moment même. Elle est pleine de nouveautés comme aucune autre version auparavant. Son introduction méritait quatre articles distincts. Dans le premier, nous allons jeter un coup d'oeil à ce qu'elle apporte au niveau du langage.
Avant de nous plonger dans le PHP, il faut savoir que la version actuelle de Nette est entièrement préparée pour la huitième version. De plus, en guise de cadeau, une Nette 2.4 entièrement compatible a été publiée, donc du point de vue du framework, rien ne vous empêche de l'utiliser.
Arguments nommés
Commençons tout de suite par une bombe, qui pourrait être audacieusement désignée comme un changement de jeu. Les arguments peuvent désormais être transmis aux fonctions et aux méthodes non seulement de manière positionnelle, mais aussi en fonction de leur nom. Ce qui est absolument génial dans le cas où une méthode a vraiment trop de paramètres :
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
) {
...
}
}
Les deux premiers arguments sont passés de manière positionnelle, les autres par leur nom : (les noms doivent suivre les positionnels)
$response->setCookie('lang', $lang, sameSite: 'None');
// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
Avant l'introduction de cette fonctionnalité, il était prévu de créer une
nouvelle API pour l'envoi de cookies dans Nette, car le nombre d'arguments pour
setCookie()
a vraiment augmenté et la notation positionnelle
était déroutante. Ce n'est plus nécessaire, car les arguments nommés sont
dans ce cas l'API la plus pratique. L'IDE les indiquera et il y a une sécurité
de type.
Ils conviennent parfaitement même pour expliquer des arguments logiques, où
leur utilisation n'est pas nécessaire, mais un simple true
ou
false
ne suffit pas :
// before
$db = $container->getService(Database::class, true);
// now
$db = $container->getService(Database::class, need: true);
Les noms des arguments font désormais partie de l'API publique. Il n'est plus possible de les modifier à volonté. Pour cette raison, même Nette subit un audit pour déterminer si tous les arguments ont un nom approprié.
Les arguments nommés peuvent également être utilisés en combinaison avec les variadics :
function variadics($a, ...$args) {
dump($args);
}
variadics(a: 1, b: 2, c: 3);
// $args will contain ['b' => 2, 'c' => 3]
Le tableau $args
peut maintenant contenir même des clés non
numériques, ce qui est en quelque sorte une rupture BC. Il en va de même pour
le comportement de call_user_func_array($func, $args)
, où les
clés du tableau $args
jouent désormais un rôle beaucoup plus
important. Au contraire, les fonctions de la famille func_*()
sont
protégées des arguments nommés.
Les arguments nommés sont étroitement liés au fait que l'opérateur splat
...
peut maintenant développer les tableaux associatifs :
variadics(...['b' => 2, 'c' => 3]);
Étonnamment, il ne fonctionne pas à l'intérieur des tableaux pour le moment :
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
La combinaison d'arguments nommés et de variadics permet d'avoir enfin une
syntaxe fixe, par exemple pour la méthode link()
, à laquelle nous
pouvons passer des arguments nommés ainsi que des arguments positionnels :
// 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);
La syntaxe des arguments nommés est beaucoup plus sexy que l'écriture de
tableaux, aussi Latte
l'a immédiatement adoptée, où elle peut être utilisée, par exemple,
dans les balises {include}
et {link}
:
{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}
Nous reviendrons sur les arguments nommés dans la troisième partie de la série, en relation avec les attributs.
Une expression peut lancer une exception
Lancer une exception est maintenant une expression. Vous pouvez l'envelopper
dans des parenthèses et l'ajouter à une condition if
. Hmmm, cela
ne semble pas très pratique. Cependant, ceci est beaucoup plus
intéressant :
// 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');
Les fonctions flèches ne pouvant jusqu'à présent contenir qu'une seule expression, elles peuvent désormais lancer des exceptions grâce à cette fonctionnalité :
// only single expression
$fn = fn() => throw new \Exception('oops');
Expression de la correspondance
L'instruction switch-case
présente deux inconvénients
majeurs :
- elle utilise une comparaison non stricte
==
au lieu de===
- vous devez faire attention à ne pas oublier
break
par erreur
Pour cette raison, PHP propose une alternative sous la forme d'une nouvelle
expression match
, qui utilise une comparaison stricte et,
inversement, n'utilise pas break
.
switch
Exemple de code :
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;
}
Et le même (seulement avec une comparaison stricte) écrit en utilisant
match
:
$message = match ($statusCode) {
200, 300 => $this->formatMessage('ok'),
400 => $this->formatMessage('not found'),
500 => $this->formatMessage('server error'),
default => 'unknown status code',
};
Notez que match
n'est pas une structure de contrôle comme
switch
, mais une expression. Dans l'exemple, nous assignons sa
valeur résultante à une variable. En même temps, les “options”
individuelles sont également des expressions, il n'est donc pas possible
d'écrire plusieurs étapes, comme dans le cas de switch
.
En cas d'absence de correspondance (et il n'y a pas de clause par défaut),
l'exception UnhandledMatchError
est levée.
À propos, il existe également des balises {switch}
,
{case}
et {default}
dans Latte. Leur fonction
correspond exactement à la nouvelle match
. Elles utilisent la
comparaison stricte, ne nécessitent pas break
et il est possible
de spécifier plusieurs valeurs séparées par des virgules dans
case
.
Opérateur Nullsafe
Le chaînage optionnel vous permet d'écrire une expression dont
l'évaluation s'arrête si elle rencontre null. C'est grâce au nouvel
opérateur ?->
. Il remplace une grande partie du code qui,
autrement, devrait vérifier de manière répétée la présence de null :
$user?->getAddress()?->street
// approximately translates to
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
Pourquoi “approximativement” ? Parce qu'en réalité, l'expression est
évaluée de manière plus ingénieuse afin qu'aucune étape ne soit répétée.
Par exemple, $user->getAddress()
n'est appelé qu'une seule
fois, de sorte que le problème causé par la méthode renvoyant quelque chose
de différent la première et la deuxième fois ne peut être rencontré.
Cette fonctionnalité a été apportée par “Latte” il y a un an:https://blog.nette.org/…om-functions. Maintenant, c'est PHP lui-même qui l'adopte. Super.
Promotion des propriétés des constructeurs
Un sucre syntaxique qui évite d'écrire le type deux fois et la variable quatre fois. Dommage qu'il ne soit pas arrivé à une époque où nous n'avions pas d'IDE aussi intelligents qui l'écrivent aujourd'hui pour nous 🙂
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,
) {}
}
Il fonctionne avec Nette DI, vous pouvez commencer à l'utiliser.
Vérification plus stricte des types d'opérateurs arithmétiques et binaires
Ce qui aidait autrefois les langages de script dynamiques à s'imposer est devenu leur point faible. Autrefois, PHP s'est débarrassé des “guillemets magiques”, de l'enregistrement des variables globales et aujourd'hui, le comportement détendu est remplacé par la rigueur. Le temps où, en PHP, vous pouviez ajouter, multiplier, etc. presque tous les types de données pour lesquels cela n'avait aucun sens, est révolu. Depuis la version 7.0, PHP devient de plus en plus strict et depuis la version 8.0, toute tentative d'utiliser des opérateurs arithmétiques ou binaires sur des tableaux, des objets ou des ressources se termine par une TypeError. L'exception est l'ajout de tableaux.
// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Des comparaisons plus saines entre chaînes et nombres
Ou rendez l'opérateur libre à nouveau génial.
Il semblerait qu'il n'y ait plus de place pour l'opérateur loose
==
, qu'il s'agisse simplement d'une faute de frappe lors de
l'écriture de ===
, mais ce changement le remet sur le devant de la
scène. Si nous l'avons déjà, laissons-le se comporter raisonnablement. À la
suite de la comparaison “déraisonnable” précédente, par exemple,
in_array()
pourrait vous troller désagréablement :
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns false
Le changement de comportement de ==
concerne la comparaison de
nombres et de chaînes “numériques” et est présenté dans le tableau
suivant :
Comparaison | Avant | PHP 8.0 |
---|---|---|
0 == "0" |
vrai | vrai |
0 == "0.0" |
vrai | vrai |
0 == "foo" |
vrai | faux |
0 == "" |
vrai | faux |
42 == " 42" |
vrai | vrai |
42 == "42 " |
vrai | vrai |
42 == "42foo" |
vrai | faux |
42 == "abc42" |
vrai | faux |
"42" == " 42" |
vrai | vrai |
"42" == "42 " |
faux | vrai |
De manière surprenante, c'est une rupture de la CB au cœur même de la langue, qui a été approuvée sans aucune résistance. Et c'est une bonne chose. JavaScript pourrait être très jaloux à cet égard.
Rapports d'erreurs
De nombreuses fonctions internes déclenchent maintenant TypeError et
ValueError au lieu d'avertissements qui étaient faciles à ignorer. Un certain
nombre d'avertissements du noyau ont été reclassés. L'opérateur d'arrêt
@
ne tait plus les erreurs fatales. Et PDO lance des exceptions par
défaut.
Nette a toujours essayé de résoudre ces choses d'une manière ou d'une autre. Tracy a modifié le comportement de l'opérateur shutup, Database a changé le comportement de PDO, Utils contient des remplacements pour les fonctions standards, qui lancent des exceptions au lieu d'avertissements faciles à manquer, etc. C'est agréable de voir que la direction stricte que Nette a dans son ADN devient la direction native du langage.
Incrémentation des clés de tableau négatives
$arr[-5] = 'first';
$arr[] = 'second';
Quelle sera la clé du deuxième élément ? Avant, c'était 0
,
since PHP 8 it’s -4
.
Virgule de fin
Le dernier endroit où la virgule de fin ne pouvait pas être, était la définition des arguments de fonction. C'est une chose du passé :
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // trailing comma
) {
....
}
$object::class
La constante magique ::class
fonctionne également avec les
objets $object::class
, remplaçant complètement la fonction
get_class()
.
Attraper les exceptions uniquement par type
Et enfin : il n'est pas nécessaire de spécifier une variable pour l'exception dans la clause catch :
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // no $e
$logger->log('....');
}
Dans les prochaines parties, nous verrons les innovations majeures concernant les types de données, nous montrerons ce que sont les attributs, quelles nouvelles fonctions et classes sont apparues en PHP et nous présenterons le compilateur Just in Time.
Pour soumettre un commentaire, veuillez vous connecter