PHP 8.0 : Nouvelles fonctions, classes et JIT (4/4)

il y a 4 ans de David Grudl  

La version 8.0 de PHP est sortie. Elle est pleine de nouvelles fonctionnalités comme aucune autre version auparavant. Leur présentation méritait quatre articles distincts. Dans la dernière partie, nous jetterons un coup d'œil aux nouvelles fonctions et classes et nous présenterons le compilateur Just in Time.

Nouvelles fonctions

La bibliothèque PHP standard comporte des centaines de fonctions et dans la version 8.0, six nouvelles fonctions sont apparues. Cela ne semble pas beaucoup, mais la plupart d'entre elles remédient aux points faibles du langage. Ce qui s'inscrit parfaitement dans le concept de la version 8.0, qui resserre et consolide PHP comme aucune autre version auparavant. Un aperçu de toutes les nouvelles fonctions et méthodes peut être trouvé dans le guide de migration.

str_contains() str_starts_with() str_ends_with()

Fonctions permettant de déterminer si une chaîne de caractères commence, se termine ou contient une sous-chaîne.

if (str_contains('Nette', 'te')) {
	...
}

Avec l'avènement de cette trinité, PHP définit comment gérer une chaîne vide lors d'une recherche, ce à quoi toutes les autres fonctions connexes adhèrent, et qui est une chaîne vide est trouvée partout:.

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Grâce à cela, le comportement de la trinité est complètement identique aux analogues de Nette :

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Pourquoi ces fonctions sont-elles si importantes ? Les bibliothèques standard de tous les langages sont toujours marquées par l'évolution historique ; les incohérences et les faux pas ne peuvent être évités. Mais en même temps, c'est un témoignage de la langue respective. Il est surprenant de constater que le PHP, vieux de 25 ans, manque de fonctions pour des opérations aussi basiques que le retour du premier ou du dernier élément d'un tableau, l'échappement du HTML sans mauvaises surprises (htmlspecialchars n'échappe pas une apostrophe), ou simplement la recherche d'une chaîne dans une chaîne. Il n'est pas possible de le contourner d'une manière ou d'une autre, car le résultat n'est pas un code lisible et compréhensible. C'est une leçon pour tous les auteurs d'API. Lorsque vous voyez qu'une grande partie de la documentation de la fonction est occupée par des explications sur les pièges (comme les valeurs de retour de strpos), c'est un signe clair pour modifier la bibliothèque et ajouter str_contains.

get_debug_type()

Remplace le désormais obsolète get_type(). Au lieu des types longs comme integer, il renvoie l'actuel int, dans le cas des objets, il renvoie directement le type :

Value gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Migration des ressources vers les objets

Les valeurs du type ressource viennent d'une époque où PHP n'avait pas encore d'objets, mais en avait besoin. C'est ainsi que les ressources sont nées. Aujourd'hui, nous avons les objets et, comparés aux ressources, ils fonctionnent beaucoup mieux avec le garbage collector, donc le plan est de les remplacer progressivement par des objets.

A partir de PHP 8.0, les ressources images, curl joins, openssl, xml, etc. sont changées en objets. En PHP 8.1, les connexions FTP, etc. suivront.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Ces objets n'ont pas encore de méthodes, et vous ne pouvez pas les instancier directement. Jusqu'à présent, il s'agit simplement de se débarrasser des ressources obsolètes de PHP sans modifier l'API. Et c'est une bonne chose, car créer une bonne API est une tâche distincte et difficile. Personne ne souhaite la création de nouvelles classes PHP telles que SplFileObject avec des méthodes nommées fgetc() ou fgets().

PhpToken

Le tokenizer et les fonctions autour de token_get_all sont également migrés vers des objets. Cette fois, il ne s'agit pas de se débarrasser des ressources, mais d'obtenir un objet à part entière représentant un jeton PHP.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

La méthode isIgnorable() retourne vrai pour les jetons T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, et T_OPEN_TAG.

Cartes faibles

Les cartes faibles sont liées au ramasseur d'ordures, qui libère de la mémoire tous les objets et valeurs qui ne sont plus utilisés (c'est-à-dire qu'aucune variable ou propriété ne les contient). Comme les threads PHP sont de courte durée et que nous disposons de beaucoup de mémoire sur nos serveurs, nous n'abordons généralement pas les questions relatives à la libération effective de la mémoire. Mais pour les scripts plus longs, ils sont essentiels.

L'objet WeakMap est similaire à SplObjectStorage. Tous deux utilisent des objets comme clés et permettent de stocker des valeurs arbitraires sous ces objets. La différence est que WeakMap n'empêche pas l'objet d'être libéré par le ramasseur de déchets. C'est-à-dire que si le seul endroit où l'objet existe actuellement est une clé dans la carte faible, il sera supprimé de la carte et de la mémoire.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

A quoi cela sert-il ? Par exemple, pour la mise en cache. Prenons une méthode loadComments() à laquelle nous passons un article de blog et qui renvoie tous ses commentaires. Comme la méthode est appelée à plusieurs reprises pour le même article, nous allons créer une autre getComments(), qui mettra en cache le résultat de la première méthode :

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

L'idée est que lorsque l'objet $article est libéré (par exemple, l'application commence à travailler avec un autre article), son entrée est également libérée du cache.

PHP JIT (Just in Time Compiler)

Vous savez peut-être que PHP est compilé en ce que l'on appelle des opcodes, qui sont des instructions de bas niveau que vous pouvez voir ici, par exemple et qui sont exécutées par une machine virtuelle PHP. Et qu'est-ce qu'un JIT ? Le JIT peut compiler PHP de manière transparente directement en code machine, qui est exécuté directement par le processeur, de sorte que l'exécution plus lente par la machine virtuelle est contournée.

Le JIT est donc destiné à accélérer PHP.

L'effort pour implémenter le JIT dans PHP remonte à 2011 et est soutenu par Dmitry Stogov. Depuis, il a essayé 3 implémentations différentes, mais aucune d'entre elles ne s'est retrouvée dans une version finale de PHP pour trois raisons : le résultat n'a jamais été une augmentation significative des performances pour les applications web typiques ; complique la maintenance de PHP (c'est-à-dire que personne à part Dmitry ne le comprend 😉 ) ; il y avait d'autres moyens d'améliorer les performances sans avoir à utiliser un JIT.

Le saut d'augmentation des performances observé dans la version 7 de PHP était un sous-produit du travail sur le JIT, bien que paradoxalement il n'ait pas été déployé. Ce n'est qu'aujourd'hui que cela se produit en PHP 8. Mais je retiens des attentes exagérées : vous ne verrez probablement pas d'accélération.

Alors pourquoi le JIT fait-il son entrée en PHP ? Tout d'abord, les autres moyens d'améliorer les performances s'épuisent lentement, et le JIT est simplement l'étape suivante. Dans les applications web courantes, il n'apporte aucune amélioration de la vitesse, mais il accélère significativement, par exemple, les calculs mathématiques. Cela ouvre la possibilité de commencer à écrire ces choses en PHP. En fait, il serait possible d'implémenter directement en PHP certaines fonctions qui nécessitaient auparavant une implémentation directe en C pour des raisons de vitesse.

Le JIT fait partie de l'extension opcache et est activé avec elle dans le php.ini (lisez la documentation sur ces quatre chiffres) :

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

Vous pouvez vérifier que le JIT est en cours d'exécution, par exemple, dans le panneau d'information de la barre Tracy.

Le JIT fonctionne très bien si toutes les variables ont des types clairement définis et ne peuvent pas changer même en appelant le même code à plusieurs reprises. Je me demande donc si un jour nous déclarerons également des types en PHP pour les variables : string $s = 'Bye, this is the end of the series';