Appel d'une fonction asynchrone en PHP

J’ai toujours cru que PHP savait aussi faire des appels de fonction de façon asynchrone (en). Je ne l’avais pas essayé auparavant, mais j’étais persuadé qu’il existait une fonction du type async_call() ou même un mot clé réservé qui permettait de dire à PHP :

Cette fonction là, lorsque je l’appelle, tu n’attends pas sa réponse.

Mais en fait non… Même pas. Croyez-le ou non mais même si PHP sait faire beaucoup de chose, il ne sait pas faire ça. Vous ne pouvez pas lancer un gros traitement en temps masqué pendant que vous répondez aux autres attentes de vos utilisateurs.

C’est bien simple : Tant qu’il n’a pas terminé ce que vous lui avez demandé, PHP ne vous rend pas la main. Ce qui peut vite devenir désagréable pour les utilisateurs de vos services.

Requêtes HTTP : la feinte

Mon astuce, que je vous fournis gracieusement, est d’appeler la fonction que l’on veut asynchrone grâce à une requête HTTP. Comme si c’était l’utilisateur qui avait changé de page. Par contre, dès la requête lancée on « coupe » la liaison HTTP, on ferme le socket pour ne pas attendre la réponse.

Dans l’un de mes projets, l’utilisateur peut cliquer sur un bouton qui va exécuter une fonction PHP. Mais cette fonction est lourde et prends beaucoup de temps pour rendre une réponse. Mon idée était donc de couper cette fonction en deux : une fonction qui enregistrerait les infos de l’utilisateur et qui traiterait les tâches légères, puis une autre qui s’occuperait des tâches lourdes, mais dont on n’a pas besoin d’attendre la réponse.

C’est la première fonction qui enverra la requête HTTP pour lancer la seconde fonction. Ce qui signifie aussi que votre « fonction asynchrone » doit être accessible directement depuis une URL de votre application quelle que soit la méthode utilisée.

Un exemple en POST

Je ne traiterais pas de l’accessibilité de la fonction asynchrone. Ni de cette fonction en elle-même. Je vous explique les étapes. Vous devez juste comprendre le principe pour pouvoir l’appliquer à vos projets.

J’ai décidé de séparer mon code en trois fonctions distinctes pour simplifier et clarifier le tout. Une fonction qui est appelée directement par l’utilisateur grâce à l’interface. La fonction que l’on veut lancer de façon asynchrone. Et une fonction qui fera le traitement du JSON, des sockets HTTP et de tout le reste.

Les deux premières fonctions :

<?php

function launch(){ 
    // traitement des tâches légère 
    curlPostAsync('http://example.com/lauch/progress/', array('var' => 'content')); 
   return true; 
} 

function launchProgress($data){ 
   // traitement des tâches lourdes 
}

La fonction des traitements de sockets :

<?php

function curlPostAsync($addr,$data){ 
    $post_string = json_encode($data); 
    $url = parse_url($addr); 
    $errno = ''; 
    $errstr = ''; 

    $fp = fsocketopen($url['host'], 80, $errno, $errstr, 30); 

    $out = "POST ".$url['path']." HTTP/1.1\r\n"; 
    $out.= "Host: ".$url['host']."\r\n"; 
    $out.= "Content-Type: text/json\r\n"; 
    $out.= "Content-Length: ".strlen($post_string)."\r\n"; 
    $out.= "Connection: Close\r\n"; 
    $out.= $post_string; 

    fwrite($fp, $out); 
    fclose($fp);
 
    return 0; 
}

Comme vous l’avez sûrement remarqué, dès qu’on a écrit notre requête sur le socket on ferme celui-ci. On rend donc la main à l’utilisateur alors qu’un traitement (la fonction asynchrone) est toujours en train de s’effectuer sur le serveur. Magique !