Alors que PHP 8.1 va sortir à la fin de l’année, et que la version 21.10 d’Ubuntu vient d’être mise en ligne en embarquant PHP 8, je me suis dit que ça pourrait être bien de revenir sur toutes les nouveautés qui sont apparues dans le langage PHP depuis que PHP 7 a été présenté en décembre 2015.
Mon but ne va pas être de lister toutes les fonctionnalités qui sont apparues dans PHP 7, 7.1, 7.2, 7.3, 7.4 et 8 ; il y en a beaucoup trop, certaines étant très pointues et pensées pour des usages très particuliers. Je vais me contenter de parler de celles qui me paraissent les plus intéressantes, celles que j’utilise réellement dans mes développements.
PHP 7 (décembre 2015)
PHP 7 était une amélioration majeure, qui apportait notamment une réécriture du moteur interne et de la gestion des tableaux, amenant une augmentation importante des performances.
Il faut savoir que le code qui sert à gérer les tableaux (et les tableaux associatifs, car en PHP c’est la même chose) est utilisé en interne par la machine virtuelle PHP pour stocker tous les symboles (fonctions, objets, variables, etc.) ; donc améliorer la performance des tableaux, ça n’est pas utile que lorsque votre code manipule des tableaux, ça améliore toute l’exécution de PHP.
Si le sujet vous intéresse, et pour simplifier la chose à l’extrême, disons que les éléments d’un tableau (au sens PHP) sont stockés dans un tableau (au sens C) via une fonction de hachage ; mais pour pouvoir garder l’ordre dans lequel les éléments sont ajoutés, il faut constituer une liste. Auparavant, cela se faisait avec une liste doublement chaînée, alors que désormais cela utilise un tableau (au sens C). Les listes chaînées ne consomment que la mémoire nécessaire, mais l’allocation mémoire nécessaire à chaque ajout d’élément prend du temps ; l’utilisation de tableaux est beaucoup plus rapide, mais consomme un peu plus de mémoire (car on alloue de la place en prévision des éléments futurs).
Utilisation des types scalaires dans les déclarations de paramètres
Cela fait longtemps qu’on peut déclarer les types des paramètres attendus par les fonctions en méthodes, sauf que cela se limitait aux objets (et aux dérivés comme self et parent), aux interfaces, aux tableaux (array) et aux fonctions (callable). En PHP 7, il est possible de déclarer qu’un paramètre est de type string, int, float, ou bool. Ça a l’air idiot, mais c’est difficile de s’en passer.
function toto(string $s) {
// dans la fonction, on est sûr que $s est une chaîne
}
toto('abc'); // OK
toto(123); // OK sauf en mode strict
toto(new MonObjet()); // erreur Langage du code : PHP (php)
Déclaration du type de retour
Dans les fonctions et méthodes, PHP 7 permet de définir le type de retour. Comme pour les paramètres, PHP fera par défaut les conversions de type lorsqu’elles sont possibles (de int vers string, par exemple), sauf si on utilise le mode strict (si la fonction retourne une valeur du mauvais type, cela génère une erreur).
Ça permet d’avoir du code beaucoup plus propre.
function toto() : int { ... }
$a = toto(); // on est sûr de récupérer un entierLangage du code : PHP (php)
L’opérateur NULL coalescent
J’étais dubitatif sur l’utilité de cet opérateur, mais je l’utilise maintenant hyper souvent. Il fait l’équivalent d’un opérateur ternaire avec un isset() dans la condition.
$a = isset($_POST['valeur']) ? $_POST['valeur'] : $default;
$a = $_POST['valeur'] ?? $default; // équivalent à la ligne précédenteLangage du code : PHP (php)
L’opérateur spaceship
Pas le truc le plus révolutionnaire, mais une nouvel opérateur de comparaison fait son apparition. Après les égal à (==), inférieur à (<), supérieur à (>), inférieur ou égal à (<=) et supérieur ou égal à (>=), l’opérateur spaceship (<=>, nommé ainsi parce qu’il ressemble à une soucoupe volante) retourne -1, 0 ou 1 suivant que le premier élément est respectivement inférieur, égal ou supérieur au second élément comparé.
Le cas d’usage typique est avec une fonction de tri :
$users = [
['name' => 'Alice', 'age' => 25],
['name' => 'Bob', 'age' => 18],
['name' => 'Camille', 'age' => 32],
];
usort($users, function($a, $b) {
return $a['age'] <=> $b['age'];
});Langage du code : PHP (php)
Les classes anonymes
De la même manière qu’on peut créer des fonctions anonymes, les classes anonymes permettent de créer des objets à la volée. Il ne faut clairement pas en abuser, mais ça peut être utile dans certains cas.
$o = new class {
public function toto() {
// ...
}
};
$o->toto();Langage du code : PHP (php)
Les classes anonymes peuvent implémenter des interfaces :
LogManager::setLogger(new class implements LoggerInterface {
// ...
});Langage du code : PHP (php)
azeae
PHP 7.1 (décembre 2016)
Type nullable
Aussi bien dans les définitions de paramètres que pour les types de retour, il devient possible de dire que la valeur peut être soit du type demandé, soit nulle, en mettant un point d’interrogation devant le type.
C’est devenu indispensable.
function toto(?int $i) { ... }
toto(3); // OK
toto(null); // OKLangage du code : PHP (php)
Pseudo-type void dans les retours de fonctions
Les fonctions qui ne doivent jamais rien retourner peuvent prendre le pseudo-type void.
Ce n’est pas un truc révolutionnaire, mais ça permet là encore de rendre le code plus propre.
Déconstruction symétrique des tableaux
Cette fonctionnalité n’est pas hyper-utile, mais c’est le genre de sucre syntaxique qu’on est content d’avoir quand on en a besoin. Ça rend le code plus lisible, c’est toujours bon à prendre.
list($alice, $bob, $camille) = $users;
[$alice, $bob, $camille] = $users; // équivalent à la ligne précédenteLangage du code : PHP (php)
Visibilité des constantes de classe
Il est maintenant possible de définir la visibilité des constantes de classe.
class Toto {
public const AAA = 1;
protected const BBB = 2;
private const CCC = 3;
}Langage du code : PHP (php)
Pseudo-type iterable
Ce pseudo-type peut s’utiliser dans les paramètres et les retours de fonctions. Il accepte les valeurs qui sont des tableaux ou qui sont des objets qui implémentent l’interface Traversable. C’est très pratique parce qu’on se retrouve souvent à manipuler des valeurs qui peuvent être indifféremment l’un ou l’autre (notamment dans des bibliothèques).
catch multiple
Cette fonctionnalité permet de regrouper dans une même instruction catch plusieurs types d’exceptions. Ça évite d’avoir à écrire plusieurs catch avec exactement le même code.
Là encore, c’est du sucre syntaxique. Mais quand on a pris l’habitude, ça devient indispensable.
try {
// ...
} catch (UneException | AutreException $e) {
// ...
}Langage du code : PHP (php)
PHP 7.2 (novembre 2017)
Ben… il y a pas mal de nouveautés qui sont apparues dans PHP 7.2, mais aucune je n’utilise vraiment au quotidien.
PHP 7.3 (décembre 2018)
Virgules de fin dans les appels de fonctions
Dans les appels de fonctions et de méthodes, il est maintenant possible de laisser traîner une virgule après le dernier paramètre. Comme pour les tableaux, ça permet d’ajouter des entrées supplémentaires avec des diff de commit plus propres.
toto(
$param1,
$param2,
);Langage du code : PHP (php)
Amélioration de Heredoc et Nowdoc
Les syntaxes Heredoc et Nowdoc, qui servent à définir des longues chaînes de caractères (Nowdoc étant à Heredoc ce que les apostrophes sont aux guillemets pour les chaînes de caractères classiques), peuvent maintenant voir leur label de fin être indenté ; l’indentation sera alors supprimées de leur contenu.
function affiche() {
echo <<<FIN
AA
BB
CC
FIN;
}Langage du code : PHP (php)
Affichera :
AA
BB
CC
PHP 7.4 (novembre 2019)
Typage des propriétés de classes
Il est maintenant possible de spécifier le type des propriétés dans les objets.
Une fois qu’on y a goûté, c’est comme pour le typage des paramètres et des retours de fonctions, on ne peut plus s’en passer.
class Toto {
private int $entier;
private string $chaine;
}Langage du code : PHP (php)
Opérateur d’assignation de fusion null
Derrière ce nom barbare se cache un opérateur très pratique, qui évite d’utiliser un opérateur ternaire ou un opérateur null coalescent (voir plus haut).
$a = isset($a) ? $a : $b;
$a = $a ?? $b; // équivalent à la ligne précédente
$a ??= $b; // équivalent aux deux lignes précédentesLangage du code : PHP (php)
Fonctions fléchées
Les fonctions fléchées permettent de créer des fonctions anonymes avec une syntaxe légère. Elles s’écrivent avec une syntaxe du type fn(paramètres) => expression;
Contrairement aux fonctions anonymes classiques, les fonctions fléchées héritent du contexte de là où elles sont créées. Elles ont notamment accès aux variables du scope (y compris $this) sans avoir besoin de les déclarer avec use.
Il est possible de créer une fonction fléchée qui n’a pas accès au contexte en utilisant le mot-clé static.
class Demo {
private int $base = 10;
public function run() {
// a accès au contexte
$f = fn($x) => $this->base + $x;
return $f(5);
}
public function runStatic() {
// n'a pas accès au contexte
$f = static fn($x) => $this->base + $x;
return $f(5);
}
}
$demo = new Demo();
print($demo->run()); // fonctionne
print($demo->runStatic()); // erreurLangage du code : PHP (php)
Gestion de la variance dans l’héritage
Lorsqu’une classe hérite d’une autre, la surcharge de méthodes obéit au principe de substitution de Liskov :
- les paramètres peuvent être ouverts à un type parent
- les retours peuvent être limités à un sous-type
class A {}
class B extends A { }
class ObjetParent {
public function operation(B $o) : A {
// ...
}
}
class ObjetEnfant {
public function operation(A $o) : B {
// ...
}
}Langage du code : PHP (php)
Déballage de tableaux
Là, il s’agit de lister automatiquement tous les éléments d’un tableau.
C’est du sucre syntaxique qui n’est pas utile tous les jours, mais qui nous économise plusieurs lignes de code quand on en a besoin.
$t1 = [3, 4];
$t2 = [1, 2, ...$t1, 5, 6]; // équivalent à [1, 2, 3, 4, 5, 6]Langage du code : PHP (php)
Underscores dans les nombres
C’est le genre de petit truc qui facilite tellement la lecture du code qu’on se demande comment on faisait avant.
$couleurs = 16_777_216; // plus lisible que 1677216
$max32bits = 4_294_967_296; // plus lisible que 4294967296Langage du code : PHP (php)
Préchargement de code dans OPCache
Cette fonctionnalité permet d’ajouter dans la configuration PHP un script dont le contenu sera lu au lancement de l’interpréteur, et dont le code sera chargé dans le cache d’opcode.
Quand on maîtrise un serveur et qu’on veut accélérer le chargement d’une application web, c’est très efficace.
Références faibles
Les références que l’ont crée avec l’opération esperluette (&) on pour effet que la mémoire allouée pour une donnée n’est libérée qu’une fois que toutes les références sont détruites.
$toto1 = new Toto();
$toto2 = &$toto1;
unset($toto1); // la mémoire est toujours allouée à cause de $toto2
var_dump($toto2); // affiche l'objetLangage du code : PHP (php)
Les références faibles permettent de garder une référence sur une donnée, mais sans empêcher la libération de la mémoire.
$toto1 = new Toto();
$toto2 = WeakReference::create($toto1);
unset($toto1); // la mémoire est libérée
var_dump($toto2->get()); // affiche nullLangage du code : PHP (php)
Méthodes magiques __serialize() et __unserialize()
J’ai toujours trouvé que l’utilisation des méthodes magiques __sleep() et __wakeup() était bancale. L’interface Sérializable apportait les méthodes serialize() et unserialize(), mais c’est plus logique d’ajouter des méthodes magiques supplémentaires plutôt que d’être obligé d’implémenter une interface.
PHP 8 (décembre 2020)
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 strpos() 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)
Expression match
On peut voir les expressions match comme étant une modernisation du switch, mélangée avec un opérateur ternaire. La comparaison est stricte, équivalente à un triple-égal (===) et non pas un double-égal (==), et l’expression retourne une valeur (qu’on n’est pas obligé d’utiliser).
$color = 'red';
$text = match ($color) {
'green' => 'vert',
'red', 'magenta' => 'rouge',
'blue', 'indigo' => 'bleu',
$mainColor => 'couleur principale',
default => 'autre',
};Langage du code : PHP (php)
Il est possible de passer true en valeur de comparaison, puis de faire des comparaisons spécifiques.
Voici un exemple tiré de la documentation PHP :
$age = 18;
$output = match (true) {
$age < 2 => "Bébé",
$age < 13 => "Enfant",
$age <= 19 => "Adolescent",
$age >= 40 => "Adulte âgé",
$age > 19 => "Jeune adulte",
};Langage du code : PHP (php)
Il est aussi possible d’appeler des fonctions :
$result = match($var) {
$aaa => toto(), // toto() est exécutée si $var === $aaa
tata() => titi(), // tata() est exécutée si $var !== $aaa
// titi() est exécutée si $var !== $aaa && $var === tata()
};Langage du code : PHP (php)
Attributs
Voilà une évolution importante du langage. C’est (enfin ?) une intégration officielle qui va permettre de ne plus utiliser des annotations. J’avoue que je n’aime pas les annotations : c’est du bricolage, on utilise l’introspection de PHP pour lire les commentaires textuels et tenter de les interpréter.
Les attributs PHP s’inspirent grandement des attributs en C#. Ils permettent de définir un objet, qui est associé à un objet ou une méthode, et qui pourra être récupéré et exécuté (typiquement par le framework qui exécute l’objet ou la méthode), permettant éventuellement de modifier le comportement de l’appelant. L’API de réflexion permet de récupérer les attributs qui sont définis.
On imagine assez bien que tous les usages pour lesquels on passait par des annotations seront remplacés par des attributs.
Par exemple, pour qu’une action (méthode d’un contrôleur dans un framework MVC) ne soit exécutée que pour les requêtes POST (et donc génère une erreur pour les requêtes GET, HEAD, PUT…), on pourrait avoir quelque chose de la forme :
use \Framework\Attributes\Post;
class MonControleur {
#[Post]
public function monAction() {
// ...
}
}Langage du code : PHP (php)
Le futur
PHP 8.1 va arriver en novembre 2021. Pas mal de nouvelles fonctionnalités vont apparaître à leur tour.
Personnellement, ce que j’attends le plus de cette version est l’arrivée des énumérations. Oui, je parle bien de la fonctionnalité qui existe dans le C depuis la nuit des temps, qui a mis 10 ans à arriver dans le Java, 25 ans à arriver dans le Python, et donc aussi 25 ans à arriver dans le PHP (on l’attend encore dans le Perl et le Ruby…).
Les énumérations, ça permet de rendre son code vraiment beaucoup plus propre dans certains cas. Ça évite d’utiliser des chaînes de caractères ou des nombres en constantes, avec toutes les erreurs de comparaison qui en découle (parce que les comparaisons se font sur les valeurs uniquement et pas sur le type).
La propriété readonly pourra être utilisée avec les attributs d’objets. Ainsi, un attribut (dont la valeur ne change plus une fois qu’elle est affectée) pourra être laissé en accès public, mais en lecture seule, évitant d’avoir à écrire un getter juste pour lire la valeur.
La fonction array_is_list() permettra de savoir facilement si un tableau est utilisé comme une simple liste numérique ou non. Là encore, c’est une aide mineure, mais qui peut faire gagner du temps en quelques rares occasions.
Les fibres (“fibers”) permettront de faire du code asynchrone en PHP. Là, j’attends de jouer avec avant de me faire une opinion.
Edit de novembre 2025 : je vous invite à lire mon article De PHP 8.0 à PHP 8.5, retour sur cinq ans d’innovation. qui aborde les nouveautés de PHP sur les 4 années qui ont suivi la publication du présent article.
Mais pourquoi avoir rajouté ce « case » dans les enum. Ca n’a aucun sens , ca va les rendre pénible à écrire … Autant continuer à écrire une classe abstraite pleine de const ca revient au même 🙁
Très bon résumé de toutes les dernières fonctionnalités dont beaucoup que je n’utilise pas encore…
@Grunk : Bah les mots-clés « case » ne sont pas si dérangeants que ça. En tout cas, pas au point de dire que ça devient pénible à écrire.
Parce que ta classe pleine de constantes, c’est bien beau, mais ça veut dire qu’au final tu va quand même passer des valeurs scalaires (chaîne ou entier) en paramètre de tes fonctions, avec tous les risques que ça implique.
Exemple :
class UserRightsEnum {public const NO_ACCESS = 0;
public const READ_ONLY = 1;
public const READ_WRITE = 2;
public const ADMIN = 3;
}
class User {
public function setRights(int $access) {
// ...
}
}
// ...
$rights = UserRightsEnum::ADMIN;
$user->setRights($rights); // ça marche
$rights = 25;
$user->setRights($rights); // ça passe alors qu'il ne faudrait pas !
Alors qu’avec les énumérations, ton code est blindé parce que tu bénéficies de la vérification de type du langage.
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 type
@Amaury , on est d’accord , mais je pense qu’on est d’accordpour dire que opter pour la syntaxe C++/JAVA aurait été plus simple que faire comme swift :
enum UserRights {
NO_ACCESS;
READ_ONLY;
READ_WRITE;
ADMIN;
}
Le case , ici n’apporte aucun intérêt surtout quand la syntaxe sans ce case est éprouvée depuis des années dans des langages de référence dont php s’inspire déjà beaucoup.
@Grunk : oui, oui, mais bon… il y a tellement de bizarreries dans tous les langages, je ne vais pas m’offusquer pour si peu. C’est déjà bien que les énumérations arrivent en PHP.
En espérant que les gens les utilisent : j’ai vu tellement de code C écrit par des gens connus et reconnus, qui utilisent encore des constantes là où il faudrait de toute évidence des typedef enum… alors que c’est dans le langage depuis 1989 !
Super article, merci pour cet aperçu vraiment bien utile.
Merci pour ce récapitulatif très complet !
PHP8 est tellement formidable qu’on a des tas de messages d’erreur ! Bravo la compatibilité descendante !
En ce moment je me débats avec « Trying to access array offset on value of type int »…
@JMB Personnellement, j’ai eu extrêmement peu de problèmes en passant à PHP 8.
Là, vu votre message d’erreur, j’aurais tendance à penser que le code devait être bancal, vu que vous essayez de lire un entier comme si c’était un tableau…
Je vous conseille d’utiliser un outil d’analyse statique du code, comme PHPStan, ça pourrait vous aider grandement dans la recherche de bugs.