En effet, PHP n'offre aucun moyen de supprimer une telle fonction de rappel une fois qu'elle a été définie, puisque la fonction unregister_shutdown_function()
n'existe pas.
Évidemment, certain esprits chagrins vont dire que cela n'est pas étonnant puisque PHP est un langage de merde notamment parce qu'il est incohérent, mais là n'est pas la question car cela n'apporte aucune solution au problème.
Et il y a effectivement une solution qui consiste à faire appel à une fermeture lexicale.
Une fermeture lexicale est une fonction anonyme dans laquelle est injectée, au moment de la sa définition, une ou plusieurs variables externes grâce au mot-clef use
, de la manière suivante :
<?php $fonctionAnonyme = function() {}; $fermetureLexicale = function() use ($foo) {}; ?>
Dans le cas général, la valeur des variables injectées lorsque la fonction est exécutée est celle qu'elles avaient lors de la définition de la fermeture lexicale :
<?php $foo = "c'est fou !"; $fermetureLexicale = function() use ($foo) { echo $foo . PHP_EOL; }; $foo = 'on va au bar ?'; $fermtureLexicale(); // Affiche « c'est fou ! » ?>
Il est cependant possible de modifier ce comportement grâce à l'opérateur &
, de la manière suivante :
<?php $foo = "c'est fou !"; $fermetureLexicale = function() use (& $foo) { echo $foo . PHP_EOL; }; $foo = 'on va au bar ?'; $fermtureLexicale(); // Affiche « on va au bar ? » ?>
Grâce à ce mécanisme, Il est donc possible de piloter le comportement d'une fonction de rappel passé à register_shutdown_function()
.
Il suffit en effet de modifier son comportement en fonction de la valeur d'une variable injecté via le combo use
et &
, de la manière suivante :
<?php $phpEstDeLaMerde = true; register_shutdown_function(function() use (& $phpEstDeLaMerde) { if ($phpEstDeLaMerde) echo 'PHP est de la merde !' . PHP_EOL; } ); $phpEstDeLaMerde = false; // « PHP est de la merde ! » ne s'affichera pas ?>
Avec un peu d'astuce, il est donc bien possible de faire en sorte qu'une fonction passée à register_shutdown_function()
n'est aucun effet, ou bien présente un comportement différent en fonction des valeurs de une ou plusieurs variables susceptibles d'être modifiées au cours de l'exécution du script.
6 réactions
1 De Fabien - 10/08/2012, 16:37
C'est vrai que ça manque cruellement de symétrie. En PHP plus vieux que 5.3, on sera obligé de se rabattre sur une variable globale (snif) ou un attribut statique (mieux).
La fonction register_shutdown_function est super utile, en effet, et elle est même lancée en cas d'erreur fatale et de dépassement de temps PHP. Mais par contre, j'ai pas trouvé moyen de la lancer en cas de dépassement mémoire !
2 De sanpi - 10/08/2012, 16:38
Idée intéressante, mais j'ai du mal à voir une application concrète.
Ça fonctionne bien dans ton exemple, mais si on utilise du code un peu plus proche de la réalité, ça marche moins bien.
Le problème est contournable en utilisant une propriété de classe, du coup une simple méthode suffit, non ?
Par contre je n'ai pas compris l'histoire des tests unitaires :/
3 De mageekguy - 10/08/2012, 16:46
@sanpi : Ça marche effectivement aussi avec une propriété de classe mais j'avais envie de parler des fermetures lexixcales, et de plus, parfois, il n'est pas intéressant ou justifié de faire une méthode de classe juste pour qu'elle soit appelée via
register_shutdown_function()
.Et c'est normal que ton exemple ne marche pas puisque je n'ai dit nul part que cela marchait même si on ne tenait pas compte de la portée des variables.
4 De Arnapou - 10/08/2012, 23:03
Je privilégie dans mon cas l'utilisation de l'EventDispatcher de Symfony2.
J'instancie un dispatcher qui déclenche un event donné dans la fonction de rappel utilisée dans le register_shutdown_function. Il suffit d'ajout ou de retirer des listeners au dispatcher comme on veut et si le dispatcher n'a rien à éxécuter et bien il ne se passera rien dans le exit de php.
Par contre, à noter qu'en CLI, un CTRL+C du script PHP enverra un SIGINT et donc pas de register_shutdown_function exécuté. Si vous voulez catcher le SIGINT il vous faut l'implémenter vous-même et ce catch fera un exit propre qui lui-même déclenchera les fonctions de rappel de register_shutdown_function.
5 De Moosh - 11/08/2012, 10:07
Je me souviens d'avoir testé un truc avec register_shutdown_function http://le.brol.de.moosh.be/blog/ind...
en fait le maximum_execution_time ne joue pas sur le code exécuté par register_shutdown_function
(mais je n'étais pas du tout clair dans mon post:) )
il faudrait que je resteste
6 De Matthieu - 11/08/2012, 10:12
Cette solution sonne un peu comme une variable globale je trouve, une propriété de classe me semble un peu mieux.
Comme tu le disais, tu voulais parler des fermetures lexicales, tu as bien fait j'ai appris le "use (& $foo)"
Et sinon, @Fabien "j'ai pas trouvé moyen de la lancer en cas de dépassement mémoire", moi non plus pas de solution probante pour le moment : http://stackoverflow.com/questions/...