PHP 8 a été officiellement mis à la disposition du public le 26 novembre 2020 !
Cette nouvelle mise à jour majeure apporte tout un tas d’optimisations et de puissantes fonctionnalités au langage et nous sommes heureux de vous présenter les changements les plus intéressants qui nous permettront d’écrire un meilleur code et de construire des applications plus puissantes.
Êtes-vous prêt ? Plongeons dans le vif du sujet !
PHP JIT (Compilation Just in Time)
La fonctionnalité la plus acclamée de PHP 8 est la compilation à la volée (Just In Time). Qu’est-ce que JIT ?
La proposition de RFC décrit JIT comme suit :
« PHP JIT est mis en œuvre comme une partie presque indépendante d’OPcache. Il peut être activé / désactivé au moment de la compilation et de l’exécution de PHP. Lorsqu’il est activé, le code natif des fichiers PHP est stocké dans une région supplémentaire de la mémoire partagée d’OPcache et op_array→opcodes[].handler(s) conserve les pointeurs vers les points d’entrée du code JIT-ed ».
Alors, comment en sommes-nous arrivés à JIT et quelle est la différence entre JIT et OPcache ?
Pour mieux comprendre ce qu’est JIT pour PHP, examinons rapidement comment PHP s’exécute du code source au résultat final.
L’exécution de PHP est un processus en 4 étapes :
- Lexing/Tokenizing : Tout d’abord, l’interprèteur lit le code PHP et construit un ensemble de jetons.
- Parsing : L’interprèteur vérifie si le script correspond aux règles syntaxiques et utilise des jetons pour construire un arbre syntaxique abstrait (Abstract Syntax Tree ou AST), qui est une représentation hiérarchique de la structure du code source.
- Compilation : L’interprèteur parcourt l’arbre et traduit les nœuds AST en opcodes Zend de bas niveau, qui sont des identificateurs numériques déterminant le type d’instruction exécuté par la VM Zend.
- Interprétation : Les opcodes sont interprétés et exécutés sur la VM Zend.
L’image suivante montre une représentation visuelle du processus d’exécution de base de PHP.
Alors, comment OPcache rend-il PHP plus rapide ? Et quels sont les changements dans le processus d’exécution avec JIT ?
L’extension OPcache
PHP est un langage interprété. Cela signifie que lorsqu’un script PHP est exécuté, l’interprèteur analyse, compile et exécute le code à chaque requête. Cela peut entraîner une perte de ressources CPU et du temps supplémentaire.
C’est là que l’extension OPcache entre en jeu :
« OPcache améliore les performances de PHP en stockant le bytecode des scripts pré-compilés dans la mémoire partagée, ce qui évite à PHP de charger et d’analyser les scripts à chaque requête. »
Lorsque OPcache est activé, l’interpréteur PHP passe par les quatre étapes mentionnées ci-dessus uniquement la première fois que le script est exécuté. Comme les bytecodes PHP sont stockés dans la mémoire partagée, ils sont immédiatement disponibles en tant que représentation intermédiaire de bas niveau et peuvent être exécutés immédiatement sur la VM Zend.
À partir de PHP 5.5, l’extension Zend OPcache est disponible par défaut et vous pouvez vérifier si vous l’avez correctement configurée en appelant simplement phpinfo()
depuis un script sur votre serveur ou en consultant votre fichier php.ini (voir les réglages de configuration d’OPcache).
Lecture suggérée : Comment améliorer la limite de mémoire PHP dans WordPress.
Préchargement
OPcache a été récemment amélioré avec la mise en œuvre du préchargement, une nouvelle fonctionnalité d’OPcache ajoutée avec PHP 7.4. Le préchargement permet de stocker un ensemble spécifique de scripts dans la mémoire d’OPcache « avant l’exécution de tout code d’application », mais il n’apporte pas d’amélioration tangible des performances des applications web typiques.
Vous pouvez en savoir plus sur le préchargement dans notre introduction à PHP 7.4.
Avec JIT, PHP fait un pas en avant.
JIT – La compilation à la volée
Même si les opcodes se présentent sous la forme d’une représentation intermédiaire de bas niveau, ils doivent encore être compilés en code machine. JIT « n’introduit pas de forme IR (Intermediate Representation) supplémentaire », mais utilise DynASM (Assembleur dynamique pour les moteurs de génération de code) pour générer du code natif directement à partir du byte-code PHP.
En bref, JIT traduit les parties chaudes du code intermédiaire en code machine. En contournant la compilation, il serait en mesure d’apporter des améliorations considérables en termes de performances et d’utilisation de la mémoire.
Zeev Surasky, co-auteur de la proposition de PHP JIT, montre à quel point les calculs seraient plus rapides avec JIT :
Mais, JIT améliorerait-il efficacement les performances de WordPress ?
JIT pour les applications web en direct
Selon la RFC de JIT, la mise en œuvre du compilateur « just in time » devrait améliorer les performances de PHP. Mais connaîtrions-nous vraiment de telles améliorations dans des applications de la vie réelle comme WordPress ?
Les premiers tests montrent que JIT permettrait de faire fonctionner beaucoup plus rapidement les charges de travail gourmandes en CPU, mais la RFC met en garde :
« … comme les tentatives précédentes – cela ne semble pas actuellement améliorer de manière significative les applications de la vie réelle comme WordPress (avec opcache.jit=1235 326 req/sec vs 315 req/sec).
Il est prévu de fournir un effort supplémentaire, en améliorant JIT pour les applications de la vie réelle, en utilisant le profilage et les optimisations spéculatives ».
Avec JIT activé, le code ne serait pas exécuté par la VM Zend, mais par l’unité centrale elle-même, ce qui améliorerait la vitesse de calcul. Les applications web comme WordPress reposent également sur d’autres facteurs comme le TTFB, l’optimisation des bases de données, les requêtes HTTP, etc.
Ainsi, en ce qui concerne WordPress et les applications similaires, il ne faut pas s’attendre à une grande augmentation de la vitesse d’exécution de PHP. Néanmoins, JIT pourrait apporter plusieurs avantages aux développeurs.
« Les avantages du compilateur JIT sont en gros (et comme déjà souligné dans le RFC) :
- Des performances nettement meilleures pour le code numérique.
- Légèrement plus performant pour le code PHP d’application web « typique ».
- La possibilité de faire passer davantage de code de C à PHP, car PHP sera désormais suffisamment rapide ».
Ainsi, alors que JIT n’apportera guère d’améliorations considérables aux performances de WordPress, il fera passer PHP au niveau supérieur, en en faisant un langage dans lequel de nombreuses fonctions pourraient désormais être écrites directement.
L’inconvénient, cependant, serait la plus grande complexité qui peut entraîner une augmentation des coûts de maintenance, de stabilité et de débogage. Selon Dmitry Stogov :
« JIT est extrêmement simple, mais de toute façon il augmente le niveau de complexité du PHP, le risque de nouveaux types de bugs et le coût du développement et de la maintenance. »
La proposition d’inclure JIT dans PHP 8 a été adoptée par 50 voix contre 2.
Améliorations et nouvelles fonctionnalités de PHP 8
En dehors de JIT, nous pouvons nous attendre à de nombreuses fonctionnalités et améliorations avec PHP 8. La liste suivante est notre sélection des ajouts et changements à venir qui devraient rendre PHP plus fiable et plus efficace.
Constructor Property Promotion
Suite à une discussion en cours sur la façon d’améliorer l’ergonomie des objets en PHP, le RFC « Constructor Property Promotion » propose une nouvelle syntaxe plus concise qui simplifiera la déclaration de propriété, la rendant plus courte et moins redondante.
Cette proposition ne concerne que les “promoted parameters” ou paramètres promus, c’est-à-dire les paramètres de méthode préfixés par des mots-clés de visibilité publique, protégée et privée.
Actuellement, toutes les propriétés doivent être répétées plusieurs fois (au moins quatre fois) avant que nous puissions les utiliser avec des objets. Considérons l’exemple suivant, tiré du RFC :
class Point {
public int $x;
public int $y;
public int $z;
public function __construct(
int $x = 0,
int $y = 0,
int $z = 0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
Selon Nikita Popov, l’auteure du RFC, nous devons écrire le nom de la propriété au moins quatre fois à trois endroits différents : la déclaration de propriété, les paramètres du constructeur et l’affectation de la propriété. Cette syntaxe n’est pas particulièrement utilisable, surtout dans les classes ayant un bon nombre de propriétés et des noms plus descriptifs.
Ce RFC propose de fusionner le constructeur et la définition des paramètres. Ainsi, à partir de PHP 8, nous disposons d’une façon plus utilisable de déclarer les paramètres et le code vu ci-dessus peut changer comme indiqué ci-dessous :
class Point {
public function __construct(
public int $x = 0,
public int $y = 0,
public int $z = 0,
) {}
}
Et c’est tout. Nous avons donc une nouvelle façon de promouvoir les propriétés qui est plus courte, plus lisible et moins sujette aux erreurs. Selon Nikita :
C’est une simple transformation syntaxique que nous faisons. Mais cela réduit la quantité de code passe-partout que vous devez écrire pour les objets de valeur en particulier…
La déclaration de propriété est transformée comme nous avions explicitement déclaré ces propriétés et nous pouvons utiliser l’API Reflection pour introspecter les définitions de propriété avant l’exécution (voir Desugaring) :
La réflexion (et autres mécanismes d’introspection) permettra d’observer l’état après le “desugaring”. Cela signifie que les propriétés promues apparaîtront de la même manière que les propriétés explicitement déclarées, et que les arguments du constructeur promu apparaîtront comme des arguments du constructeur ordinaire.
// before desugaring
class Point {
public function __construct(public int $x = 0) {}
}
// after desugaring
class Point {
public int $x;
public function __construct(int $x = 0) {
$this->x = $x;
}
}
Inheritance
Nous n’avons aucune limite à l’utilisation de l’héritage en conjonction avec des paramètres promus. De toute façon, il n’y a pas de relation particulière entre les constructeurs de classes de parents et d’enfants. Selon Nikita :
Habituellement, nous disons que les méthodes doivent toujours être compatibles avec la méthode mère. […] mais cette règle ne s’applique pas au constructeur. Ainsi, le constructeur appartient réellement à une seule classe, et les constructeurs entre la classe parent et la classe enfant n’ont pas à être compatibles d’une quelconque manière.
Voici un exemple:
class Test {
public function __construct(
public int $x = 0
) {}
}
class Child extends Test {
public function __construct(
$x,
public int $y = 0,
public int $z = 0,
) {
parent::__construct($x);
}
}
Ce qui n’est pas autorisé avec les propriétés promues
Les propriétés promues sont autorisées dans les constructeurs et les traits non abstraits, mais il y a plusieurs limitations qui méritent d’être mentionnées ici.
Constructeurs abstraits
Les propriétés promues ne sont pas autorisées dans les classes et les interfaces abstraites :
abstract class Test {
// Error: Abstract constructor.
abstract public function __construct(private $x);
}
interface Test {
// Error: Abstract constructor.
public function __construct(private $x);
}
Nullabilité
L’une des contraintes les plus notables est liée à l’annulabilité. Auparavant, lorsque nous utilisions un type qui n’était pas explicitement annulable, mais avec une valeur par défaut nulle, le type était implicitement annulable. Mais avec les types de propriété, nous n’avons pas ce comportement implicite car les paramètres promus nécessitent une déclaration de propriété, et le type annulable doit être explicitement déclaré. Voir l’exemple suivant de la RFC :
class Test {
// Error: Using null default on non-nullable property
public function __construct(public Type $prop = null) {}
// Correct: Make the type explicitly nullable instead
public function __construct(public ?Type $prop = null) {}
}
Callable Type
Comme le type callable n’est pas pris en charge pour les propriétés, nous ne sommes pas autorisés à utiliser le type callable dans les propriétés promues :
class Test {
// Error: Callable type not supported for properties.
public function __construct(public callable $callback) {}
}
Le mot-clé var n’est pas autorisé
Seul un mot-clé de visibilité peut être utilisé avec les paramètres promus, donc la déclaration des propriétés du constructeur avec le mot-clé var
n’est pas autorisée (voir l’exemple suivant du RFC) :
class Test {
// Error: "var" keyword is not supported.
public function __construct(var $prop) {}
}
Aucune duplication autorisé
Nous pouvons combiner des propriétés promues et des propriétés explicites dans la même classe, mais les propriétés ne peuvent pas être déclarées deux fois :
class Test {
public string $prop;
public int $explicitProp;
// Correct
public function __construct(public int $promotedProp, int $arg) {
$this->explicitProp = $arg;
}
// Error: Redeclaration of property.
public function __construct(public string $prop) {}
}
Les paramètres variadiques ne sont pas autorisés
La raison en est que le type déclaré est différent du paramètre variadique, qui est en fait un tableau :
class Test {
// Error: Variadic parameter.
public function __construct(public string ...$strings) {}
}
Lectures complémentaires
Pour en savoir plus sur Costructor Property Promotion, écoutez cette interview de Nikita Popov. Pour un aperçu détaillé de l’ergonomie des objets en PHP, voir ce post et l’interview suivante avec Larry Garfield.
Validation pour les méthodes de traits abstraits
Les traits sont définis comme « un mécanisme de réutilisation de code dans des langages à héritage unique tels que PHP ». Ils sont généralement utilisés pour déclarer des méthodes qui peuvent être utilisées dans plusieurs classes.
Un trait peut également contenir des méthodes abstraites. Ces méthodes déclarent simplement la signature de la méthode, mais l’implémentation de la méthode doit être faite au sein de la classe qui utilise le trait.
Selon le manuel de PHP,
« Les traits prennent en charge l’utilisation de méthodes abstraites afin d’imposer des exigences à la classe exposante ».
Cela signifie également que les signatures des méthodes doivent correspondre. En d’autres termes, le type et le nombre d’arguments nécessaires doivent être les mêmes.
Quoi qu’il en soit, selon Nikita Popov, auteur de la RFC, la validation des signatures n’est actuellement imposée que de manière ponctuelle :
- Elle n’est pas imposée dans le cas le plus courant, où l’application de la méthode est fournie par l’utilisation de la classe : https://3v4l.org/SeVK3
- Elle est imposée si l’implémentation provient d’une classe parente : https://3v4l.org/4VCIp
- Elle est imposée si l’implémentation provient d’une classe enfant : https://3v4l.org/q7Bq2
L’exemple suivant de Nikita concerne le premier cas (signature non imposée) :
trait T {
abstract public function test(int $x);
}
class C {
use T;
// Allowed, but shouldn't be due to invalid type.
public function test(string $x) {}
}
Cela étant dit, cette RFC propose de toujours lancer une erreur fatale si la méthode de mise en œuvre n’est pas compatible avec la méthode des traits abstraits, quelle que soit son origine :
Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10
Cette RFC a été approuvée à l’unanimité.
Signatures de méthodes incompatibles
En PHP, les erreurs d’héritage dûes à des signatures de méthodes incompatibles déclenchent soit une erreur fatale, soit un avertissement selon la cause de l’erreur.
Si une classe implémente une interface, les signatures de méthodes incompatibles entraînent une erreur fatale. Selon la documentation sur les interfaces objet :
« La classe qui implémente l’interface doit utiliser une signature de méthode qui est compatible avec le LSP (Liskov Substitution Principle). Ne pas le faire entraînera une erreur fatale ».
Voici un exemple d’erreur d’héritage avec une interface :
interface I {
public function method(array $a);
}
class C implements I {
public function method(int $a) {}
}
Dans PHP 7.4, le code ci-dessus entraînerait l’erreur suivante :
Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7
Une fonction dans une classe enfant avec une signature incompatible lancerait un avertissement. Voir le code suivant de la RFC :
class C1 {
public function method(array $a) {}
}
class C2 extends C1 {
public function method(int $a) {}
}
Dans PHP 7.4, le code ci-dessus lancerait simplement un avertissement :
Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
Or, cette RFC propose de toujours lancer une erreur fatale pour les signatures de méthodes incompatibles. Avec PHP 8, le code que nous avons vu plus haut déclencherait ce qui suit :
Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
Tableaux commençant par un index négatif
En PHP, si un tableau commence par un index négatif (start_index < 0
), les indices suivants partiront de 0 (plus d’informations à ce sujet dans la documentation de array_fill
). Regardez l’exemple suivant :
$a = array_fill(-5, 4, true);
var_dump($a);
Dans PHP 7.4, le résultat serait le suivant :
array(4) {
[-5]=>
bool(true)
[0]=>
bool(true)
[1]=>
bool(true)
[2]=>
bool(true)
}
Maintenant, cette RFC propose de changer les choses de manière à ce que le deuxième indice soit start_index + 1
, quelle que soit la valeur de start_index
.
Dans PHP 8, le code ci-dessus donnerait le tableau suivant :
array(4) {
[-5]=>
bool(true)
[-4]=>
bool(true)
[-3]=>
bool(true)
[-2]=>
bool(true)
}
Avec PHP 8, les tableaux commençant par un index négatif modifient leur comportement. Pour en savoir plus sur les incompatibilités ascendantes dans la RFC.
Types d’union 2.0
Les types d’union (Union Types) acceptent des valeurs qui peuvent être de différents types. Actuellement, PHP ne fournit pas de support pour les types d’union, à l’exception de la syntaxe ?Type
et du type spécial iterable
.
Avant PHP 8, les types d’Union ne pouvaient être spécifiés que dans les annotations phpdoc, comme le montre l’exemple suivant de la RFC :
class Number {
/**
* @var int|float $number
*/
private $number;
/**
* @param int|float $number
*/
public function setNumber($number) {
$this->number = $number;
}
/**
* @return int|float
*/
public function getNumber() {
return $this->number;
}
}
Maintenant, la RFC des types d’Union 2.0 propose d’ajouter la prise en charge des types d’Union dans les signatures de fonctions, de sorte que nous ne dépendrons plus de la documentation en ligne, mais définirons les types d’Union avec une syntaxe T1|T2|...
à la place :
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
Comme l’explique Nikita Popov dans la RFC,
« La prise en charge des types d’Union dans le language nous permet de déplacer davantage d’informations de type du phpdoc vers les signatures de fonction, avec les avantages habituels que cela apporte :
- Les types sont effectivement imposés, de sorte que les erreurs peuvent être détectées à temps.
- Parce qu’elles sont imposées, les informations de type sont moins susceptibles de devenir obsolètes ou de manquer les « edge-cases ».
- Les types sont vérifiés lors de l’héritage, en appliquant le principe de substitution de Liskov.
- Les types sont disponibles par l’intermédiaire de Reflection.
- La syntaxe est beaucoup moins passe-partout que celle de phpdoc ».
Les types d’Union prennent en charge tous les types disponibles, avec certaines limitations :
- Le type
void
ne pourrait pas faire partie d’une union, carvoid
signifie qu’une fonction ne retourne aucune valeur. - Le type
null
n’est pris en charge que dans les types d’union, mais son utilisation en tant que type autonome n’est pas autorisée. - La notation de type nullable (
?T
) est également autorisée, c’est-à-direT|null
, mais nous ne sommes pas autorisés à inclure la notation?T
dans les types d’union (?T1|T2
n’est pas autorisé et nous devrions utiliserT1|T2|null
à la place). - Comme de nombreuses fonctions (c’est-à-dire
strpos()
,strstr()
,substr()
, etc.) incluentfalse
parmi les types de retour possibles, le pseudo-typefalse
est également pris en charge.
Vous pouvez en savoir plus sur les types d’Union V2 dans la RFC.
Erreurs de type cohérentes pour les fonctions internes
Lors du passage d’un paramètre de type illégal, les fonctions internes et celles définies par l’utilisateur se comportent différemment.
Les fonctions définies par l’utilisateur provoquent une TypeError
, mais les fonctions internes se comportent de différentes manières, selon plusieurs conditions. Quoi qu’il en soit, le comportement typique est de lancer un avertissement et de retourner null
. Voir l’exemple suivant en PHP 7.4 :
var_dump(strlen(new stdClass));
Il en résulterait l’avertissement suivant :
Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL
Si strict_types
est activé, ou si les informations sur les arguments spécifient les types, le comportement sera différent. Dans de tels scénarios, l’erreur de type est détectée et entraîne une TypeError
.
Cette situation entraînerait un certain nombre de problèmes bien expliqués dans la section des questions de la RFC
.
Pour supprimer ces incohérences, cette RFC propose de faire en sorte que les APIs d’analyse des paramètres internes génèrent toujours une ThrowError
en cas de non-concordance d’un type de paramètre.
Dans PHP 8, le code ci-dessus provoque l’erreur suivante :
Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
thrown in /path/to/your/test.php on line 4
Expression throw
Dans PHP, throw
est une déclaration, il n’est donc pas possible de l’utiliser dans les endroits où seule une expression est autorisée.
Cette RFC propose de convertir la déclaration throw
en une expression afin qu’elle puisse être utilisée dans tout contexte où les expressions sont autorisées. Par exemple, les fonctions arrow, l’opérateur coalesce, les opérateurs ternary et elvis operators, etc.
Voyez les exemples suivants de la RFC :
$callable = fn() => throw new Exception();
// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();
Weak Maps
Weak Map est un ensemble de données (objets) dans lequel les clés sont faiblement référencées, ce qui signifie qu’elles ne sont pas empêchées d’être collectées.
PHP 7.4 a ajouté la prise en charge des références faibles comme moyen de conserver une référence à un objet qui n’empêche pas l’objet lui-même d’être détruit. Comme l’a souligné Nikita Popov,
« Les références brutes faibles n’ont qu’une utilité limitée en soi et les weak maps sont beaucoup plus couramment utilisées dans la pratique. Il n’est pas possible de mettre en œuvre une weak map efficace en plus des références faibles de PHP parce que la possibilité d’enregistrer un callback de destruction n’est pas prévue ».
C’est pourquoi cette RFC introduit une classe WeakMap
pour créer des objets à utiliser comme clés de weak map qui peuvent être détruites et retirées de la weak map s’il n’y a pas d’autres références à l’objet clé.
Dans les processus de longue durée, cela permettrait d’éviter les fuites de mémoire et d’améliorer les performances. Voir l’exemple suivant de la RFC :
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);
Avec PHP 8, le code ci-dessus produirait le résultat suivant (voir le code en action ici) :
object(WeakMap)#1 (1) {
[0]=>
array(2) {
["key"]=>
object(stdClass)#2 (0) {
}
["value"]=>
int(42)
}
}
Si vous désactivez l’objet, la clé est automatiquement retirée de la weak map :
unset($obj);
var_dump($map);
Le résultat serait maintenant le suivant :
object(WeakMap)#1 (0) {
}
Pour un examen plus approfondi des weaks maps, voir la RFC. La proposition a été approuvée à l’unanimité.
Virgule de fin dans la liste des paramètres
Les virgules de fin de ligne sont des virgules annexées à des listes d’éléments dans différents contextes. PHP 7.2 a introduit les virgules de fin de liste dans la syntaxe des listes, PHP 7.3 a introduit les virgules de fin de liste dans les appels de fonction.
PHP 8 introduit désormais des virgules de fin de ligne dans les listes de paramètres avec les fonctions, les méthodes et les fermetures, comme le montre l’exemple suivant :
class Foo {
public function __construct(
string $x,
int $y,
float $z, // trailing comma
) {
// do something
}
}
Cette proposition a été adoptée par 58 voix contre 1.
Allow :: Syntaxe de classe sur les objets
Afin d’obtenir le nom d’une classe, nous pouvons utiliser la syntaxe Foo\Bar::class
. Cette RFC propose d’étendre la même syntaxe aux objets, de sorte qu’il est maintenant possible de récupérer le nom de la classe d’un objet donné, comme le montre l’exemple ci-dessous :
$object = new stdClass;
var_dump($object::class); // "stdClass"
$object = null;
var_dump($object::class); // TypeError
Avec PHP 8, $object::class
fournit le même résultat que get_class($object)
. Si $object
n’est pas un objet, il lance une exception TypeError
.
Cette proposition a été approuvée à l’unanimité.
Attributs v2
Les attributs, également appelés annotations, sont une forme de métadonnées structurées qui peuvent être utilisées pour spécifier les propriétés des objets, des éléments ou des fichiers.
Jusqu’à la version 7.4 de PHP, les doc-comments étaient le seul moyen d’ajouter des métadonnées aux déclarations de classes, fonctions, etc. Maintenant, la RFC Attributs v2 introduit des attributs pour PHP en les définissant comme une forme de métadonnées structurées et syntaxiques qui peuvent être ajoutées aux déclarations de classes, propriétés, fonctions, méthodes, paramètres et constantes.
Les attributs sont ajoutés avant les déclarations auxquelles ils se réfèrent. Voir les exemples suivants de la RFC :
<<ExampleAttribute>>
class Foo
{
<<ExampleAttribute>>
public const FOO = 'foo';
<<ExampleAttribute>>
public $x;
<<ExampleAttribute>>
public function foo(<<ExampleAttribute>> $bar) { }
}
$object = new <<ExampleAttribute>> class () { };
<<ExampleAttribute>>
function f1() { }
$f2 = <<ExampleAttribute>> function () { };
$f3 = <<ExampleAttribute>> fn () => 1;
Les attributs peuvent être ajoutés avant ou après un commentaire de doc-block :
<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}
Chaque déclaration peut avoir un ou plusieurs attributs et chaque attribut peut avoir une ou plusieurs valeurs associées :
<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}
Voir la RFC pour un aperçu plus détaillé des attributs PHP, des cas d’utilisation et de la syntaxe alternative. Notez que la version 2 des attributs est actuellement en cours d’implémentation.
Arguments nommés
Les arguments nommés fournissent une nouvelle façon de passer des arguments à une fonction dans le PHP :
Les arguments nommés permettent de passer des arguments à une fonction en fonction du nom du paramètre, plutôt que de la position du paramètre.
Nous pouvons passer des arguments nommés à une fonction en ajoutant simplement le nom du paramètre avant sa valeur :
callFunction(name: $value);
Nous sommes également autorisés à utiliser des mots-clés réservés, comme le montre l’exemple ci-dessous :
callFunction(array: $value);
Mais nous ne sommes pas autorisés à passer un nom de paramètre de manière dynamique. Le paramètre doit être un identifiant et la syntaxe suivante n’est pas autorisée :
callFunction($name: $value);
Selon Nikita Popov, l’auteur de ce RFC, les arguments nommés offrent plusieurs avantages.
Tout d’abord, les arguments nommés nous aideront à écrire un code plus compréhensible parce que leur signification est autodocumentée. L’exemple ci-dessous, tiré du RFC, s’explique d’lui-même :
array_fill(start_index: 0, num: 100, value: 50);
Les arguments nommés sont indépendants de l’ordre. Cela signifie que nous ne sommes pas obligés de transmettre les arguments à une fonction dans le même ordre que la signature de la fonction :
array_fill(value: 50, num: 100, start_index: 0);
Il est également possible de combiner des arguments nommés avec des arguments de position :
htmlspecialchars($string, double_encode: false);
Un autre grand avantage des arguments nommés est qu’ils permettent de spécifier uniquement les arguments que nous voulons réellement changer et que nous n’avons pas à spécifier d’arguments par défaut si nous ne voulons pas écraser les valeurs par défaut. L’exemple suivant, tiré de la RFC, le montre clairement :
htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);
.
Named arguments can be used with PHP attributes, as shown in the following example from the RFC:
Les arguments nommés peuvent être utilisés avec les attributs PHP, comme le montre l’exemple suivant de la RFC :
<<MyAttribute('A', b: 'B')>>
class Test {}
Toutefois, il n’est pas permis de faire passer des arguments de position après des arguments nommés, ce qui entraînerait une erreur de compilation. La même chose se produit lorsque l’on passe deux fois le même nom de paramètre.
Les arguments nommés sont particulièrement utiles pour les déclarations de classe car les constructeurs ont généralement un grand nombre de paramètres et les arguments nommés fournissent une manière plus « ergonomique » de déclarer une classe.
Pour en savoir plus sur les « Named Arguments », avec les contraintes, les incompatibilités rétroactives et plusieurs exemples, voir le RFC « Named Arguments ».
Opérateur Nullsafe
Ce RFC introduit l’opérateur nullsafe $->
avec évaluation complète du court-circuit.
Dans l’évaluation de court-circuit, le second opérateur n’est évalué que si le premier opérateur n’évalue pas à null
. Si un opérateur d’une chaîne est évalué à null
, l’exécution de la chaîne entière s’arrête et est évaluée à null
.
Examinez les exemples suivants du RFC :
$foo = $a?->b();
Si $a
est nul, la méthode b()
n’est pas appelée et $foo
est mis à null
.
Voir l’opérateur nullsafe RFC pour des exemples supplémentaires, les exceptions et le champ d’application futur.
Comparaisons entre les chaînes de Saner et les numéros
Dans les versions précédentes de PHP, lors d’une comparaison non stricte entre des chaînes de caractères et des nombres, PHP attribue d’abord la chaîne à un nombre, puis effectue la comparaison entre des nombres entiers ou flottants. Même si ce comportement est très utile dans plusieurs scénarios, il peut produire des résultats erronés qui peuvent également conduire à des bogues et/ou des problèmes de sécurité.
Considérons l’exemple suivant du RFC :
$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)
Le PHP 8 introduit les comparaisons entre chaînes de Saner et nombres, visant à rendre les comparaisons entre chaînes et nombres plus raisonnables. Selon les termes de Nikita Popov,
Ce RFC vise à donner aux comparaisons de chaînes de chiffres un comportement plus raisonnable : Lors de la comparaison avec une chaîne numérique, utilisez une comparaison de nombres (comme maintenant). Sinon, convertissez le nombre en chaîne de caractères et utilisez une comparaison de chaînes de caractères.
Le tableau suivant compare le comportement de la comparaison des chaînes et des numéros dans les versions antérieures de PHP et dans PHP 8 :
Comparison | Before | After ------------------------------ 0 == "0" | true | true 0 == "0.0" | true | true 0 == "foo" | true | false 0 == "" | true | false 42 == " 42" | true | true 42 == "42foo" | true | false
Pour en savoir plus sur les nombreuses implications de ce changement et sur la façon dont les comparaisons entre chaînes et nombres changent en PHP 8, lisez le RFC officiel de Nikita Popov.
Saner Chaînes numériques
En PHP, les chaînes de caractères contenant des chiffres se répartissent en trois catégories :
- Chaînes numériques : chaînes contenant un nombre éventuellement précédé d’espaces.
- Chaîne numérique de tête : chaînes dont les caractères initiaux sont des chaînes numériques et les caractères finaux des chaînes non numériques.
- Chaîne non numérique : chaînes n’entrant dans aucune des catégories précédentes.
Les chaînes numériques et les chaînes numériques principales sont traitées différemment selon l’opération effectuée. Par exemple, les chaînes numériques et les chaînes de chiffres de tête sont traitées différemment selon l’opération effectuée :
- Les conversions explicites de chaînes de caractères en nombres (c’est-à-dire les caractères
(int)
et(float)
) convertissent les nombres numériques et les chaînes de caractères à chiffres de tête. La conversion explicite d’une chaîne non numérique en un nombre produit 0. - Les conversions implicites de chaînes de caractères en nombres (c’est-à-dire sans déclaration de
strict_type
) donnent des résultats différents pour les chaînes de caractères numériques et non numériques. Les conversions de chaînes non numériques en nombres entraînent une erreur de type (TypeError
). is_numeric()
renvoie vrai uniquement pour les chaînes de caractères numériques.
Les décalages de chaîne, les opérations arithmétiques, les opérations d’incrémentation et de décrémentation, les comparaisons de chaîne à chaîne et les opérations bit par bit donnent également des résultats différents.
Ce RFC se propose de :
Unifier les différents modes de chaînes numériques en un seul concept : Caractères numériques uniquement avec les espaces avant et arrière autorisés. Tout autre type de chaîne n’est pas numérique et entraînera des erreurs typographiques lorsqu’il sera utilisé dans un contexte numérique.
Cela signifie que toutes les chaînes qui émettent actuellement le E_NOTICE « A non well formed numeric value encountered » seront reclassées en E_WARNING « A non-numeric value encountered » sauf si la chaîne numérique de tête ne contenait que des espaces à la fin. Et les différents cas qui émettent actuellement un E_WARNING seront promus en
TypeError
s.
Pour un aperçu plus détaillé des chaînes numériques en PHP 8, avec des exemples de code, des exceptions et des problèmes de rétrocompatibilité, voir le RFC.
Expression de correspondance v2
La nouvelle expression de correspondance est match
similaire à switch
, mais avec une sémantique plus sûre et permettant de renvoyer des valeurs.
To understand the difference between the two control structures, consider the following switch
example from the RFC:
Pour comprendre la différence entre les deux structures de contrôle, examinez l’exemple de switch
suivant du RFC :
switch (1) {
case 0:
$result = 'Foo';
break;
case 1:
$result = 'Bar';
break;
case 2:
$result = 'Baz';
break;
}
echo $result;
//> Bar
Nous pouvons maintenant obtenir le même résultat que le code ci-dessus avec l’expression de match
suivante :
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};
//> Bar
Un grand avantage de l’utilisation de la nouvelle expression match
est que, alors que switch
compare les valeurs de façon lâche (==
), ce qui peut conduire à des résultats inattendus, avec match
, la comparaison est un contrôle d’identité (===
).
L’expression de match
peut également contenir plusieurs expressions séparées par des virgules, ce qui permet une syntaxe plus concise (source) :
$result = match ($x) {
// This match arm:
$a, $b, $c => 5,
// Is equivalent to these three match arms:
$a => 5,
$b => 5,
$c => 5,
};
Pour d’autres exemples et cas d’utilisation, voir le RFC Match expression v2 et la documentation PHP.
Contrôles de type plus stricts pour les opérateurs arithmétiques/bit-système
Dans les versions précédentes de PHP, l’application d’opérateurs arithmétiques et bitwise à un tableau, une ressource ou un objet non surchargé était autorisée. Quoi qu’il en soit, le comportement était parfois incohérent.
Dans ce RFC, Nikita Popov montre à quel point ce comportement peut être déraisonnable à l’aide d’un simple exemple :
var_dump([] % [42]);
// int(0)
Nikita explique comment l’application d’un opérateur arithmétique ou binaire à des tableaux, des ressources ou des objets non surchargés a conduit à des résultats différents :
Opérateurs +, -, *, /, ** :
- Lancez l’exception d’erreur sur l’opérande de tableau. (Excluant + si les deux opérandes sont des tableaux).
- Convertir silencieusement un opérande de ressource en ID de ressource sous forme d’entier.
- Convertir un opérande objet en entier un, tout en lançant un avis.
Opérateurs %, <<, >>, &, |, ^ :
- Convertir silencieusement un opérande de tableau en entier zéro s’il est vide ou en entier un s’il n’est pas vide.
- Convertir silencieusement un opérande de ressource en ID de ressource sous forme d’entier.
- Convertir un opérande objet en entier un, tout en lançant un avis.
Opérateur ~ :
- Lancez une exception d’erreur pour les opérandes de tableau, de ressource et d’objet.
Opérateurs ++ et — :
- Ne faites rien en silence si l’opérande est un tableau, une ressource ou un objet.
Avec PHP 8, les choses changent et le comportement est le même pour tous les opérateurs arithmétiques et binaires :
Lancez une exception TypeError
pour les opérandes de tableau, de ressource et d’objet.
Nouvelles fonctions PHP
PHP 8 apporte plusieurs nouvelles fonctions au langage :
str_contains
Avant PHP 8, strstr et strpos étaient les options typiques pour les développeurs qui cherchaient un needle dans une chaîne donnée. Le problème est que ces deux fonctions ne sont pas considérées comme très intuitives et que leur utilisation peut être déroutante pour les nouveaux développeurs PHP. Voir l’exemple suivant :
$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);
if ($pos !== false) {
echo "The string has been found";
} else {
echo "String not found";
}
Dans l’exemple ci-dessus, nous avons utilisé l’opérateur de comparaison !==
, qui vérifie également si deux valeurs sont du même type. Cela nous évite d’obtenir une erreur si la position de needle est 0 :
« Cette fonction peut renvoyer le booléen FALSE, mais peut également renvoyer une valeur non booléenne qui évalue à FALSE. […] Utilisez l’opérateur === pour tester la valeur de retour de cette fonction. »
En outre, plusieurs frameworks fournissent des fonctions d’aide pour rechercher une valeur à l’intérieur d’une chaîne donnée (voir la documentation de Laravel Helpers à titre d’exemple).
Maintenant, cette RFC propose l’introduction d’une nouvelle fonction permettant de rechercher à l’intérieur d’une chaîne de caractères : str_contains
.
str_contains ( string $haystack , string $needle ) : bool
Son utilisation est assez simple. str_contains
vérifie si $needle
est trouvé dans $haystack
et renvoie true
ou false
en conséquence.
Ainsi, grâce à str_contains
, nous pouvons écrire le code suivant :
$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
if (str_contains($mystring, $findme)) {
echo "The string has been found";
} else {
echo "String not found";
}
Ce qui est plus lisible et moins sujet à des erreurs (voir ce code en action ici).
Au moment de la rédaction du présent document, str_contains
est sensible à la casse, mais cela pourrait changer à l’avenir.
La proposition str_contains
a été adoptée par 43 voix contre 9.
str_starts_with() et str_ends_with()
En plus de la fonction str_contains
, deux nouvelles fonctions permettent de rechercher un needle à l’intérieur d’une chaîne donnée : str_starts_with
et str_ends_with
.
Ces nouvelles fonctions vérifient si une chaîne donnée commence ou se termine par une autre chaîne :
str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool
Les deux fonctions renvoient false
si $needle
est plus long que $haystack
.
Selon Will Hudgins, l’auteur de cette RFC,
« Les fonctionnalités
str_starts_with
etstr_ends_with
sont tellement nécessaires que de nombreux frameworks PHP majeurs les prennent en charge, notamment Symfony, Laravel, Yii, FuelPHP et Phalcon« .
Grâce à eux, nous avons pu éviter d’utiliser des fonctions sous-optimales et moins intuitives comme substr
, strpos
. Ces deux fonctions sont sensibles à la casse :
$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";
if (str_starts_with($str, "word")) echo "Not found!";
Vous pouvez voir ce code en action ici.
Cette RFC a été approuvé par 51 voix contre 4.
get_debug_type
get_debug_type
est une nouvelle fonction PHP qui renvoie le type d’une variable. La nouvelle fonction fonctionne de manière assez similaire à la fonction gettype
, mais get_debug_type
retourne les noms de type natifs et résout les noms de classe.
C’est une bonne amélioration pour le language, car gettype()
n’est pas utile pour la vérification de type.
La RFC fournit deux exemples utiles pour mieux comprendre la différence entre la nouvelle fonction get_debug_type()
et gettype()
. Le premier exemple montre gettype
en action :
$bar = [1,2,3];
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}
Avec PHP 8, nous pourrions utiliser get_debug_type
à la place :
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}
Le tableau suivant affiche les valeurs de retour de get_debug_type
et gettype
:
Valeur | gettype() | get_debug_type() |
---|---|---|
1 | integer | int |
0.1 | double | float |
true | boolean | bool |
false | boolean | bool |
null | NULL | null |
« WordPress » | string | string |
[1,2,3] | array | array |
Une classe qui s’appelle « Foo\Bar » | object | Foo\Bar |
Une classe anonyme | object | class@anonymous |
RFCs supplémentaires
Voici une liste succincte des améliorations supplémentaires approuvées pour le PHP 8 :
- Interface Stringable : cette RFC introduit une Interface Stringable qui est automatiquement ajoutée aux classes implémentant la méthode
__to String()
. Le but principal est ici d’utiliser le type d’unionstring|Stringable
. - Nouvelles API DOM Living Standard en ext/dom : cette RFC propose d’implémenter l’actuelle DOM Living Standard à l’extension PHP DOM en introduisant de nouvelles interfaces et propriétés publiques.
- Type de retour statique : PHP 8 introduit l’utilisation de
static
comme type de retour à côté des typesself
etparent
. - Ajustements de la syntaxe des variables : cette RFC résout certaines incohérences résiduelles dans la syntaxe des variables de PHP.
Benchmarks des performances de PHP 8
Si vous vous demandez quelle est la vitesse de PHP 8, nous avons la réponse. Nous avons comparé 20 plateformes/configurations PHP sur 7 versions différentes de PHP (5.6, 7.0, 7.1, 7.2, 7.3 et 8.0).
PHP 8.0 est sorti vainqueur dans la plupart des plateformes qui le prennent en charge, y compris WordPress et Laravel.
Par exemple, WordPress sur PHP 8.0 peut gérer 18,4 % de requêtes supplémentaires par seconde par rapport à PHP 7.4. De même, Laravel sur PHP 8.0 peut utiliser 8,5 % de requêtes par seconde en plus que PHP 7.3.
Si votre site web ou votre application est entièrement compatible avec PHP 8.0, vous devriez planifier la mise à jour de l’environnement de votre serveur vers PHP 8.0 dès que possible. Vous (et vos utilisateurs) apprécierez certainement ses avantages en termes de performances. Cependant, veuillez tester votre site de manière approfondie avant de procéder à la mise à jour.
Vous pouvez lire notre article sur les benchmarks PHP pour plus d’informations, comme des données détaillées sur les performances, des aperçus et de jolis graphiques !
Résumé
Quelle aventure ! Dans ce billet, nous avons couvert les optimisations et les fonctionnalités les plus intéressantes de PHP 8. La plus attendue est sûrement le compilateur Just in Time, mais il y a tellement plus avec PHP 8.
N’oubliez pas d’ajouter ce billet de blog à vos favoris pour votre future référence. 🤓
C’est maintenant votre tour : êtes-vous prêt à tester les nouvelles fonctionnalités de PHP ? Laquelle est votre préférée ? Écrivez-nous dans la section « Commentaires » ci-dessous.
Super article, très complet, merci