PHP 8.0 : Aperçu complet des nouveautés (1/4)
La version 8.0 de PHP est sortie. Elle est tellement pleine de nouveautés qu'aucune version précédente ne l'a été. Leur présentation a nécessité quatre articles distincts. Dans ce premier, nous allons examiner les nouveautés du langage.

Avant de plonger dans PHP, sachez que la version actuelle de Nette est entièrement prête pour la version 8. De plus, en cadeau, Nette 2.4 est également sorti, entièrement compatible avec elle, donc du point de vue du framework, rien ne vous empêche de commencer à utiliser la nouvelle version.
Arguments nommés
Et nous commençons directement par une bombe, qui peut être qualifiée de “game changer”. Il est désormais possible de passer des arguments aux fonctions et méthodes non seulement de manière positionnelle, mais aussi par leur nom. Ce qui est absolument génial dans le cas où une méthode a vraiment beaucoup 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
) {
...
}
}
Nous passons les deux premiers arguments de manière positionnelle, les suivants par leur nom : (les arguments nommés doivent toujours suivre les arguments positionnels)
$response->setCookie('lang', $lang, sameSite: 'None');
// au lieu de la folie
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
Avant l'arrivée de cette fonctionnalité, il était prévu de créer une
nouvelle API dans Nette pour l'envoi de cookies, car le nombre de paramètres de
setCookie()
avait vraiment augmenté et la notation positionnelle
était peu claire. Ce n'est plus nécessaire maintenant, car les arguments
nommés sont dans ce cas l'API la plus pratique. L'IDE les suggérera et ils
auront un contrôle de type.
Ils sont également parfaits pour clarifier les paramètres logiques, où
leur utilisation n'est certes pas nécessaire, mais true
ou
false
seuls ne disent pas grand-chose :
// avant
$db = $container->getService(Database::class, true);
// maintenant
$db = $container->getService(Database::class, need: true);
Les noms des paramètres font désormais partie de l'API publique. Il n'est plus possible de les modifier arbitrairement comme auparavant. C'est pourquoi Nette subit également un audit pour vérifier si tous les paramètres ont une dénomination appropriée.
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 contiendra ['b' => 2, 'c' => 3]
Le tableau $args
peut donc désormais contenir également des
clés non numériques, ce qui constitue une certaine rupture de compatibilité
(BC break). Il en va de même pour le comportement de la fonction
call_user_func_array($func, $args)
, où les clés du tableau
$args
jouent désormais un rôle significatif. En revanche, les
fonctions de la famille func_*()
sont isolées des arguments
nommés.
Les arguments nommés sont également étroitement liés au fait que
l'opérateur splat ...
peut désormais décompresser également les
tableaux associatifs :
variadics(...['b' => 2, 'c' => 3]);
Curieusement, cela ne fonctionne pas encore à l'intérieur des tableaux :
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
La combinaison d'arguments nommés et de variadics permet enfin d'avoir une
syntaxe fixe, par exemple pour la méthode link()
du presenter, à
laquelle nous pouvons désormais passer des arguments nommés de la même
manière que des arguments positionnels :
// avant
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // devait être un tableau
// maintenant
$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 paramètres nommés dans la troisième partie en relation avec les attributs.
Une expression peut lever une exception
Lever une exception est désormais une expression. Vous pouvez par exemple
l'envelopper dans des parenthèses et l'ajouter à une condition
if
. Hmmm, cela ne semble pas très pratique. Mais ceci est déjà
plus intéressant :
// avant
if (!isset($arr['value'])) {
throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];
// maintenant, quand throw est une expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');
Comme les fonctions fléchées ne peuvent jusqu'à présent contenir qu'une seule expression, grâce à cette fonctionnalité, elles peuvent lever des exceptions :
// seulement une seule expression
$fn = fn() => throw new \Exception('oops');
Expressions Match
La construction switch-case
a deux grands défauts :
- elle utilise une comparaison non stricte
==
au lieu de===
- vous devez faire attention à ne pas oublier
accidentellement
break
PHP propose donc une alternative sous la forme de la nouvelle construction
match
, qui utilise une comparaison stricte et, inversement,
n'utilise pas break
.
Exemple de code 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;
}
Et la même chose (seulement avec une comparaison stricte) écrite 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
.
S'il n'y a pas de correspondance avec l'une des options (et qu'il n'y a pas
de clause default), une exception UnhandledMatchError
est levée.
D'ailleurs, dans Latte, il existe également les balises
{switch}
, {case}
et {default}
. Leur
fonctionnement correspond exactement au nouveau match
. Elles
utilisent une comparaison stricte, ne nécessitent pas break
et
dans case
, il est possible d'indiquer plusieurs valeurs séparées
par des virgules.
Opérateur Nullsafe
Le chaînage optionnel (optional chaining) permet d'écrire une expression
dont l'évaluation s'arrête si elle rencontre null. Et ce, grâce au nouvel
opérateur ?->
. Il remplace beaucoup de code qui vérifierait
sinon de manière répétée la présence de null :
$user?->getAddress()?->street
// signifie environ
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
Pourquoi “signifie environ” ? Parce qu'en réalité, l'expression est
évaluée de manière plus ingénieuse et aucune étape n'est répétée. Par
exemple, $user->getAddress()
n'est appelé qu'une seule fois,
donc il ne peut pas y avoir de problème causé par le fait que la méthode
renverrait quelque chose de différent la première et la deuxième fois.
Cette nouveauté fraîche a été introduite par Latte il y a un an. Elle arrive maintenant dans PHP lui-même. Parfait.
Promotion des propriétés du constructeur
Du sucre syntaxique qui évite d'écrire deux fois le type et quatre fois la variable. Dommage qu'il ne soit pas arrivé à l'é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,
) {}
}
Cela fonctionne avec Nette DI, vous pouvez commencer à l'utiliser immédiatement.
Comportement strict des opérateurs arithmétiques et bitwise
Ce qui a autrefois propulsé les langages de script dynamiques au sommet est devenu avec le temps leur point le plus faible. PHP s'est autrefois débarrassé des “magic quotes”, de l'enregistrement des variables globales, et maintenant le comportement laxiste est remplacé par la rigueur. L'époque où vous pouviez en PHP additionner, multiplier, etc. presque tous les types de données pour lesquels cela n'avait absolument aucun sens est révolue depuis longtemps. À partir de la version 7.0, PHP est de plus en plus strict, et à partir de la version 8.0, toute tentative d'utilisation d'un opérateur arithmétique/bitwise sur des tableaux, des objets ou des ressources se termine par une TypeError. L'exception est l'addition de tableaux.
// opérateurs arithmétiques et bitwise
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Comparaison plus raisonnable des chaînes de caractères et des nombres
Ou : “make loose operator great again”.
Il semblerait que l'opérateur loose ==
n'ait plus sa place,
qu'il ne s'agisse que d'une faute de frappe lors de l'écriture de
===
, mais ce changement le remet sur la carte. Puisque nous
l'avons, qu'il se comporte raisonnablement. La conséquence de la comparaison
“déraisonnable” précédente était par exemple le comportement de
in_array()
, qui pouvait vous jouer un mauvais tour :
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// retournait true de manière surprenante
// à partir de PHP 8.0, retourne false
Le changement de comportement de ==
concerne la comparaison des
nombres et des chaînes “numériques” et est illustré par le tableau
suivant :
Comparaison | Avant | 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 |
Il est surprenant qu'il s'agisse d'une rupture de compatibilité (BC break) dans le fondement même du langage, qui a été approuvée sans aucune opposition. Et c'est une bonne chose. JavaScript pourrait beaucoup envier cela.
Rapport d'erreurs
De nombreuses fonctions internes lèvent désormais des TypeError et
ValueError au lieu d'avertissements qui pouvaient être facilement négligés.
De nombreux avertissements du noyau ont été reclassifiés. L'opérateur shutup
@
ne masque plus les erreurs fatales. Et PDO lève des exceptions
en mode par défaut.
Nette a toujours essayé de résoudre ces problèmes d'une manière ou d'une autre. Tracy modifiait le comportement de l'opérateur shutup, Database changeait le comportement de PDO, Utils contient des remplacements de fonctions standard qui lèvent des exceptions au lieu d'avertissements discrets, etc. Il est bon de voir que la direction stricte, qui est dans l'ADN de Nette, devient la direction native du langage.
Tableaux avec index négatif
$arr[-5] = 'first';
$arr[] = 'second';
Quelle sera la clé du deuxième élément ? Avant, c'était 0
,
depuis PHP 8, c'est -4
.
Virgule finale
Le dernier endroit où une virgule finale ne pouvait pas se trouver était la définition des paramètres de fonction. C'est désormais du passé :
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // virgule finale
) {
....
}
$object::class
La constante magique ::class
fonctionne également avec les
objets $object::class
, remplaçant ainsi complètement la fonction
get_class()
.
catch sans variable
Et enfin : dans la clause catch, il n'est pas nécessaire d'indiquer une variable pour l'exception :
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // pas de $e
$logger->log('....');
}
Dans les prochaines parties, nous découvrirons des nouveautés majeures dans les types de données, nous verrons ce que sont les attributs, quelles nouvelles fonctions et classes sont apparues en PHP, et nous présenterons le Just in Time Compiler.
Pour soumettre un commentaire, veuillez vous connecter