Il y a quatre ans, j’ai écrit un article intitulé “De PHP 7 à PHP 8, retour sur cinq ans d’innovation”. Alors que PHP 8.5 va sortir à la fin de l’année, et que les deux dernières versions d’Ubuntu (la 25.04 et la 25.10) intègrent PHP 8.4, c’est le bon moment pour revenir sur les évolutions du langage.
On peut remarquer que le nombre d’ajouts continue à augmenter à un rythme élevé. Les reproches qui pouvaient être faits au langage il y a 15-20 ans, comme quoi il stagne et n’est pas au niveau des autres langages équivalents, sont clairement caducs aujourd’hui.
Encore une fois, je ne vais pas lister toutes les nouveautés apportées par chaque version, mais uniquement les points qui me semblent utiles ou intéressants.
PHP 8.0 (décembre 2020)
Pour cette version, je vais recopier ce que j’avais écrit dans mon précédent article (vu qu’il se terminait justement avec PHP 8).
Paramètres nommés
Cette fonctionnalité sert lorsqu’on appelle une fonction ou une méthode, et permet de ne pas donner des valeurs explicites à tous les paramètres qui précèdent un paramètre dont on a besoin de donner une valeur.
C’est une fonctionnalité que j’attends depuis longtemps, qui était bien pratique en Python.
function toto(int $nbr=0, string $nom=null, int $age=null) { ... }
toto(nom: 'Alice');
toto(nom: 'Bob', nbr: 4);
toto(3, age: 100);Langage du code : PHP (php)
Union de types
Dans les définitions de paramètres et de retours de fonctions, il devient possible de lister les types autorisés.
Je ne vous dis pas à quel point c’est pratique.
function toto(int|string|null $intput) : int|float { ... }Langage du code : PHP (php)
Pseudo-type mixed
Dans les paramètres et le retour d’une fonction ou d’une méthode, il est possible d’utiliser le pseudo-type mixed, qui sert à indiquer explicitement que cela peut être n’importe quel type. Précisément, c’est un alias de l’union object|resource|array|string|int|float|bool|null.
Ça peut sembler idiot, mais ça permet de dire très clairement qu’un paramètre peut être de n’importe quel type. Ça évite de se poser la question “Le développeur a-t-il oublié de mettre le type, ou bien est-ce que ça accepte n’importe quoi ?”.
Avant ça, je mettais un commentaire /*mixed*/ à la place du type, pour être explicite. Mais c’est encore mieux que ce soit géré nativement par le langage.
Pseudo-type de retour false
Il est possible d’indiquer false comme type de retour d’une méthode, pour dire qu’elle est susceptible de retourner false (par exemple en cas d’erreur), mais qu’elle ne peut pas retourner true ; donc ce n’est pas un type de retour booléen. Le cas bien connu est celui de la fonction strpos(), qui peut retourner un nombre (éventuellement zéro) ou false (quand la chaîne recherchée n’a pas été trouvée).
À noter que ça ne peut pas être un type autonome (il faut forcément faire une union avec un autre type), et qu’il n’y a pas de pseudo-type true.
function toto() : int|false { ... }Langage du code : PHP (php)
Opérateur nullsafe ?->
Cet opérateur permet d’accéder aux attributs et méthodes d’un objet. Mais si l’élément à gauche de l’opérateur vaut null, l’attribut ou la méthode n’est pas accédée et l’ensemble retourne null.
C’est extrêmement pratique lorsqu’on veut chaîner des appels de méthodes d’un même objet, mais que ces méthodes sont susceptibles de retourner null. Habituellement, on est obligé d’ajouter plein de vérifications (et certaines personnes vous diront qu’il ne faut pas chaîner, c’est mal ; eh bien non seulement vous savez ce que je pense des intégristes, mais ils ont un argument en moins).
if (!$obj) {
$result = null;
} else {
$result = $obj->method1();
if ($result) {
$result = $result->attr1;
if ($result) {
$result = $result->method2();
}
}
}
// équivalent à tout le code ci-dessus
$result = $obj?->method1()?->attr1?->method2();Langage du code : PHP (php)
throw devient une expression
Auparavant, throw était un mot-clé du langage qui ne pouvait être utilisé que dans certaines situations. C’est désormais une expression, ce qui facilite son utilisation.
Par exemple, on peut maintenant écrire ceci :
$nom = $_POST['nom'] ?? throw new Exception("Mauvais paramètre");Langage du code : PHP (php)
Fonctions str_contains(), str_starts_with() et str_ends_with()
Ces trois fonctions permettent de savoir si une chaîne en contient un autre, si une chaîne commence par une autre, ou finit par une autre.
Ça a l’air bête, mais c’est infiniment plus lisible que d’utiliser str_pos() ou substr() comme on le faisait avant.
Promotion de constructeur
Cette fonctionnalité permet d’indiquer qu’un paramètre du constructeur doit être recopié dans un attribut de l’objet, juste en précisant la visibilité de l’attribut devant le type du paramètre. Et en faisant ça, on n’a même plus besoin de déclarer l’attribut.
Là, je dois dire que je ne m’y suis pas encore habitué. J’ai commencé à l’utiliser, mais ce n’est pas encore systématique. J’aime bien le fait de déclarer explicitement les attributs des objets, je trouve ça plus lisible que d’aller regarder dans les paramètres du constructeur. Mais on économise pas mal de lignes de code.
// avant PHP 8
class Toto {
private $a;
protected $b;
public function __construct(int $a, string $b) {
$this->a = $a;
$this->b = $b;
}
}
// à partir de PHP 8
class Toto {
public function __construct(private int $a, protected string $b) {
}
}Langage du code : PHP (php)
PHP 8.1 (novembre 2021)
Énumérations
Les énumérations permettent de spécifier des types pouvant prendre des valeurs déterminées, et ainsi éviter d’utiliser des constantes qui peuvent générer des effets de bords.
enum UserRights {
case NO_ACCESS;
case READ_ONLY;
case READ_WRITE;
case ADMIN;
}
class User {
public function setRights(UserRights $access) {
// ...
}
}
$rights = UserRights::ADMIN;
$user->setRights($rights); // ça marche
$rights = 25;
$user->setRights($rights); // erreur de typeLangage du code : PHP (php)
Pseudo-type de retour never
Un peu comme le pseudo-type void, mettre never comme type de retour d’une fonction indique qu’elle ne va non seulement jamais retourner de valeur, mais aussi qu’elle va interrompre l’exécution du programme (soit en faisant un exit, soit en appelant une fonction qui va faire un exit).
Intersection de types
PHP 8.0 apportait l’union de types, c’est-à-dire la possibilité d’indiquer plusieurs types possible pour un paramètre, un type de retour ou une propriété de classe.
Il est désormais possible d’indiquer une liste de types qui doivent tous êtres satisfaits, en utilisant le caractère esperluette (&). Évidemment, cela a du sens avec l’utilisation d’interfaces.
// la fonction attend un paramètre qui implémente à la fois
// les interfaces Avance, Tourne et Recule
function toto(Avance&Tourne&Recule $animalOuVehicule) { ... }Langage du code : PHP (php)
Fibres
Les fibres (ou “fibers” en anglais) ont pour but de permettre le traitement asynchrone via des threads virtuels (ce ne sont pas de vrais threads système) coopératifs : c’est le code qui détermine quand une fibre s’interrompt et rend la main.
Auparavant, j’ai vu du code qui utilisait les générateurs (avec le mot-clé yield) pour tenter de faire quelque chose d’approchant, mais ce n’est vraiment pas pensé pour.
Il faut bien comprendre que les fibres ne permettent pas de faire du traitement parallèle, mais bien d’interrompre et de relancer des traitements.
Exemple de code :
// création de la fibre
$fiber = new Fiber(function () {
print("Étape 1 : dans la fibre\n");
// suspend l’exécution et renvoie une valeur
$value = Fiber::suspend('Pause depuis la fibre');
print("Étape 3 : la fibre reprend avec la valeur : '$value'\n");
return 'Fin de la fibre';
});
print("Étape 0 : avant le démarrage\n");
// Démarre la fibre
$result = $fiber->start();
print("Étape 2 : fibre suspendue, valeur reçue : '$result'\n");
// Reprend la fibre en lui passant une valeur
$result = $fiber->resume('Reprise !');
print("Étape 4 : fibre terminée, valeur finale : '$result'\n");Langage du code : PHP (php)
Résultat :
Étape 0 : avant le démarrage
Étape 1 : dans la fibre
Étape 2 : fibre suspendue, valeur reçue : Pause depuis la fibre
Étape 3 : la fibre reprend avec la valeur : Reprise !
Étape 4 : fibre terminée, valeur finale : Fin de la fibre
Propriétés en lecture seule
Il est désormais possible d’ajouter le mot-clé readonly à la déclaration d’une propriété de classe. Celle-ci pour alors être initialisée une seule fois, puis ne sera pas modifiable.
class Toto {
private readonly string $name;
public function __constrcut(string $name) {
$this->name = $name
}
}Langage du code : PHP (php)
À noter qu’il est possible de l’utiliser dans la promotion de constructeur (voir PHP 8.0) :
class Toto {
public function __construct(private readonly string $name) {
}
}Langage du code : PHP (php)
Constantes de classes finales
L’utilisation du mot-clé final sur une constante de classe permet de s’assurer qu’elle ne pourra pas être surchargée dans une classe fille.
Déballage des tableaux avec des clés textuelles
On pouvait faire du déballage de tableaux contenant des clés numériques. Il est maintenant possible de mélanger clés numériques et textuelles.
$arr1 = [1, 'a' => 'b'];
$arr2 = [...$arr1, 'c' => 'd']; //[1, 'a' => 'b', 'c' => 'd']Langage du code : PHP (php)
Fonction array_is_list()
Cette fonction vérifie qu’un tableau ne contient que des clés numériques, qui commencent à zéro et qui s’incrémentent sans “trou”. Ça a l’air idiot, mais il y a des cas où c’est super pratique.
new dans les initialiseurs
Il devient possible d’utiliser des expressions comme “new MaClasse()” comme valeur par défaut d’un paramètre, d’une variable statique, de constante globale et comme argument d’attribut.
PHP 8.2 (décembre 2022)
Constantes dans les traits
Il est possible de définir des constantes dans les traits.
Classes en lecture seule
Ce code :
readonly class Toto {
public string $name;
public int $age;
}Langage du code : PHP (php)
Est équivalent à :
class Toto {
public readonly string $name;
public readonly int $age;
}Langage du code : PHP (php)
Amélioration du typage
Les types null et false pouvaient déjà être utilisés, mais uniquement en union avec d’autres types. Ce sont désormais des types autonomes, qui peuvent être utilisés seuls dans les paramètres et les retours de fonctions, ainsi que pour les propriétés de classes. S’y ajoute en plus le type true.
Il est aussi possible de mélanger unions et intersections de types. Exemple :
(Toto&Titi)|Tutu // accepte un objet qui implémente Toto et Titi, ou un objet TutuLangage du code : PHP (php)
Dépréciation des fonctions utf8_encode() et utf8_decode()
Il faut utiliser la fonction mb_convert_encoding() à la place.
PHP 8.3 (novembre 2023)
Typage des constantes de classes
Les constantes de classes peuvent maintenant être typées. C’était attendu depuis longtemps.
Évolution du readonly
Les classes anonymes peuvent maintenant être déclarées en lecture seule.
Lors du clonage d’un objet, les propriétés en lecture seule peuvent être redéfinies.
Bon, ce ne sont pas des évolutions qu’on utilise tous les jours, mais ça peut être bon de le savoir.
Fonction json_validate()
Cette nouvelle fonction permet de vérifier qu’un flux JSON est valide. Ça évite de faire un appel à json_decode(), puis de vérifier la valeur retournée par json_last_error().
Méthodes finales dans les traits
Dans les traits, il devient possible d’utiliser le mot-clé final sur les méthodes, pour empêcher qu’elles soient surchargées.
Récupération dynamique des constantes de classes
Il est maintenant possible de récupérer dynamiquement la valeur d’une constante de classe avec une syntaxe du type : MaClasse:{$nomConstante}
PHP 8.4 (novembre 2024)
Hooks de propriétés de classes
Cette fonctionnalité est une grosse évolution de la gestion des getters et setters.
Depuis très longtemps (les années 90 en Java, et même avant en C++), on a l’habitude de créer des propriétés avec une visibilité privée, en ajoutant des méthodes pour les manipuler :
class Adult {
private string $name;
private int $age;
public function setName(string $name) : void {
if (mb_strlen($name) < 2)
throw new \Exception("Nom incorrect.");
$this->name = $name;
}
public function getName() : string {
return ucfirst($this->name);
}
public function getName() : string {
return $this->name;
}
public function setAge(int $age) : void {
if ($age < 18)
throw new \Exception("Age incorrect.");
$this->age = $age;
}
public function getAge() : int {
return $this->age;
}
}Langage du code : PHP (php)
La plupart du temps, les getters n’ont aucune intelligence et se contentent de retourner la valeur de la propriété. Et bien souvent, les setters ont très peu d’intelligence, et font des vérifications assez mineures.
Avec les hooks, il devient possible de donner une visibilité publique à la propriété, en indiquant le code à exécuter lorsqu’on y accède.
class Adult {
public string $name {
set(string $name) {
if (mb_strlen($name) < 2)
throw new \Exception("Nom incorrect.");
$this->name = $name;
}
get {
return ucfirst($this->name);
}
}
public int $age {
set {
if ($value < 18)
throw new \Exception("Age incorrect.");
$this->age = $value;
}
}
}Langage du code : PHP (php)
Vous pouvez voir que si le paramètre n’est pas défini, il s’appelle $value par défaut.
Il est même possible d’utiliser des fonctions fléchées :
class Person {
public function __construct(private string $firstname,
private string $lastname) {
}
public string $fullname {
get => $this->firstname . ' ' . $this->lastname;
set {
[$this->firstname, $this->lastname] = explode(' ', $value);
}
}
public string $nickname {
set => mb_strtolower($value);
}
}Langage du code : PHP (php)
Visibilité asymétrique des propriétés
Cette évolution permet d’avoir des propriétés qui ont une visibilité différente entre la lecture et l’écriture. Le cas typique est d’avoir une propriété publique en lecture, mais privée ou protégée en écriture ; il est alors possible d’interdire l’écriture « sauvage » (il faut obligatoirement passer par un setter), tout en permettant la lecture sans passer par un getter.
class User {
public private(set) string $name;
}Langage du code : PHP (php)
À savoir qu’il est possible d’utiliser conjointement la visibilité asymétrique et les hooks de propriétés.
Je reparlerai sûrement de cette fonctionnalité dans un futur article de blog.
Objets paresseux
L’API de réflexion, qui permet de faire de l’introspection du code, a été enrichie afin de pouvoir créer des objets « paresseux », c’est-à-dire des objets dont l’initialisation est repoussée au moment où l’objet est réellement utilisé.
Dans la vie de tous les jours, ce n’est pas quelque chose qu’on utilise directement. Par contre, les composants d’injection de dépendances vont pouvoir se baser sur cette fonctionnalité pour arrêter d’instancier des centaines d’objets en cascade alors que vous n’exécutez qu’une pauvre action qui ne fait qu’une redirection.
new sans parenthèses
Il est maintenant possible d’utiliser directement une instance de classe qui vient d’être créée.
// avant
(new MonObjet())->maMethode();
// après
new MonObjet()->maMethode();Langage du code : PHP (php)
Nouvelles fonctions de manipulation de chaînes multi-octets
De nouvelles fonctions de manipulation de chaînes ont été ajoutées, qui ont la capacité d’opérer sur des caractères multi-octets :
mb_trim(),mb_ltrim()etmb_rtrim()font la même chose que les fonctionstrim(),ltrim()etrtrim()bien connues.mb_ucfirst()etmb_lcfirst()font la même chose que les fonctionsucfirst()etlcfirst()équivalentes.
Modification du comportement de exit()
Les constructions de langage exit() et die() se comportent un peu plus comme des fonctions. Elles peuvent notamment être passées en paramètre comme n’importe quel callable.
Dépréciation de certains laxismes sur les paramètres de fonctions
Il y a des comportements qui ont été dépréciés (ils fonctionnent encore mais sont découragés, car ils seront interdits dans une future version).
Le fait de donner une valeur nulle par défaut à un paramètre le rendait implicitement nullable. Il est recommandé de toujours définir explicitement le type comme nullable.
// obsolète
function toto(int $i = null) { ... }
// préférable
function toto(?int $i = null) { ... }
// ou encore
function toto(null|int $i = null) { ... }Langage du code : PHP (php)
Il est aussi devenu obsolète d’ajouter des paramètres obligatoires après des paramètres optionnels.
// obsolète
function toto(int $i = 2, int $j) { ... }
// préférable
function toto(int $j, int $i = 2) { ... }Langage du code : PHP (php)
Le futur : PHP 8.5 (novembre 2025)
PHP 8.5 va sortir à la fin de l’année, et va apporter quelques nouvelles évolutions.
L’opérateur pipe |>
Cet opérateur permet de chaîner les appels à des fonctions, en passant la sortie d’une fonction en paramètre de la suivante :
$cleanString = $string
|> trim(...)
|> mb_strtolower(...)
|> htmlentities(...);
// équivalent à
$clearString = htmlentities(mb_strtolower(trim($string)));Langage du code : PHP (php)
En fait, l’opérateur pipe peut appeler n’importe quel callable qui ne prend qu’un seul paramètre.
Fonctions array_first() et array_last()
Ces fonctions permettent de récupérer le premier et le dernier élément d’un tableau.
Nouvelle API URI
PHP 8.5 apporte une nouvelle API pour parser les URI, compatible avec la RFC 3986. Elle est plus complète − et surtout plus sécurisée − que la fonction parse_url(), au prix de plus de lignes de code pour l’utiliser.
Plus d’informations sur le site de la Fondation PHP.
Amélioration des closures
Il devient possible de récupérer une référence de la closure en cours d’exécution, permettant la récursion dans les closures.
PHP 8.5 gère aussi les closure dans des expressions constantes, ce qui permet de mettre une closure comme valeur par défaut dans les paramètres de fonctions :
function toto(
array $tableau,
Closure $callback = static function(array $a) { return array_reverse($a); }
) {
// ...
}Langage du code : PHP (php)