PHP 8.1 : un petit exemple simple avec Fiber et CURL
Article posté le 05-03-2022 dans la catégorie
PHP
Article mis à jour le : 05-05-2022
Un petit exemple pour expliquer comment utiliser Fiber, une des nouvelles fonctionnalités de PHP 8.1Ce petit article n'a pas pour vocation de réexpliquer ce qu'est Fiber. Il y a déjà plein d'articles expliquant à quoi Fiber va servir et la documentation de PHP est comme à son habitude très claire pour décrire les méthodes disponibles.
Néanmoins, rappelons quelques notions importantes :
- n'importe quelle fonction peut être executée dans une Fiber ;
- il faut que ce code soit non bloquant (qu'il ne bloque pas le thread de PHP) ;
- Fiber ne change rien au concept de PHP, ce dernier reste mono-thread ;
- rien ne donne un droit de préemption sur une Fiber, c'est à dire que rien ne peut les interrompre, elles-seules peuvent s'interrompre, nous le verrons plus loin. Cette auto-interruption veut dire que main (le script PHP qui execute des Fibers) ne peut pas les interrompre, heureusement car cela voudrait dire que main et les fibers tournent en même temps, ce qui n'est pas possible à cause du point évoqué juste avant ;
- cependant c'est bien le script principal qui pilote les Fibers, en les créant, et en leur donnant la parole les unes après les autres, comme une event-loop.
Passons maintenant à notre petit exemple concret.
Son but est de faire trois petites requêtes HTTP en parallèle. Bien entendu, nous pourrions utiliser curl_multi, natif à PHP, ou encore plus simple une librairie comme le client HTTP de Symfony, mais notre exemple ne servirait plus à rien. Non, nous allons plonger un peu en profondeur. Voici donc les étapes que nous allons suivre :
- nous lançons successivement trois requêtes HTTP ;
- elles partent vers un site perso qui utilise une temporisation sleep() pour simuler une latente réseau, d'une durée aléatoire, de 1 à 5 secondes ;
- elles sont faites en asynchrone grâce à notre code PHP piloté par Fiber.
Allons-y ! Notre exemple utilisera trois petits fichiers. Partons des profondeurs de ce code pour remonter vers le script principal, qui pilote le tout via des Fibers :
- blocking-curl-action.php
- raw-exec.php
- main.php
Voici comment cela va fonctionner : le fichier 3 utilisera le fichier 2 qui lui-même utilisera le 1 :
- main.php est notre script principal ;
- il utilise raw-exec.php pour lancer des commandes ;
- et ce dernier nous permet dans notre exemple de lancer les appels CURL via blocking-curl-action.php.
Commençons par le ... dernier, extrêmement simple : nous faisons un appel CURL bloquant à l'URL mentionnée plus haut. Pour nous permettre d'identifier chaque requête, nous passons un id numérique arbitraire. Enfin, quand la requête est terminée, avec succès, nous créons un petit fichier qui va nous servir de flag, nous verrons plus loin pourquoi :)
Passons maintenant au second, le code qui sera executé dans une Fiber : il s'agit d'une toute petite classe PHP qui contient une méthode exec() à laquelle on passe en paramètre le nom de notre fichier PHP à appeler de manière externe (comme si vous le lanciez manuellement dans la console), dans un autre thread. Vous remarquerez que :
- il attend l'id de la requête, que nous avons mentionné plus haut ;
- la commande exec() de PHP est redirigée vers une sortie spéciale pour être non bloquante : en gros, nous ne voulons pas attendre la fin de l'execution de la commande pour continuer le script, nous lançons la commande et chacun vit sa vie de son côté ;
- néanmoins, juste après, pour tirer profit de Fiber, nous faisons une boucle while() pour vérifier que la commande est bien terminée en regardant si le fameux fichier vide évoqué plus haut existe bien. Sinon, nous suspendons la Fiber pour ne pas bloquer le thread de PHP.
- enfin, pour la forme, nous affichons un message pour dire que la requête est terminée.
Enfin pour terminer, voici le fichier main.php qui pilote tout cela.
- Pour repartir de zéro, nous supprimons sauvagement (oui, vous avez vu le @...) les fichiers vides des requêtes précédentes ;
- nous créons des Fibers, qui vont utiliser la méthode execute() de la classe contenue dans le fichier raw-exec.php ;
- ensuite nous bouclons sur les Fibers, nous les remettons en route avec l'appel à resume() jusqu'à ce qu'elle soit terminées.
Pour rappel et petite explication : l'appel à resume() est aussitôt annulé par l'appel à suspend() dans le fichier raw-exec.php si la requête HTTP n'est pas terminée. Ainsi, nous pouvons faire de l'asynchrone en attendant le résultat des trois requêtes, donc sans bloquer notre script principal. Évidemment, répétons-le, ce code est juste là à titre d'exemple, si nous avions voulu faire des appels HTTP asynchrones, nous aurions en réalité utilisé une librairie qui gère déjà tout cela.
À présent, à votre tour, vous pouvez lancer le fichier :
php main.php
Voici ce que vous devriez avoir (faites plusieurs tests, le résultat va varier).
Request with id #2 is finished
Request with id #3 is finished
Request with id #1 is finished
Cet article vous a plu? Découvrez d'autres articles :