Dans le cadre de mon projet, j'ai le code suivant :

class foo
{
protected $methods = array();

public function __set($method, \closure $closure)
{
if (array_key_exists($method, $this->methods) === false)
{
throw new \logicException('Method \'' . $method . '\' does not exist');
}
}
}

Ainsi, si je veux affecter une fermeture à une méthode qui n'est pas connue de l'objet, une exception sera lancée.

Ce code est utilisé par un processus PHP, créé à l'aide de proc_open().

Ce processus doit communiquer le résultat de son exécution, représenter par un score, à son processus père.

Pour cela, il écrit sur la sortie standard le résultat de la sérialisation de son score, et la sortie standard est ensuite lue et interprétée par le processus père.

Or, ce score stocke les éventuelles exceptions générées lors de l’exécution du processus.

Donc, si le code du processus fait appel à la méthode foo::__set() avec un nom de méthode invalide qui génère une exception, cette dernière est stockée dans le score.

Et lorsque PHP crée une exception, il stocke dans cette dernière la pile d’exécution qui a conduit à sa création.

Cette pile contient, entre autre chose, les arguments qui ont été passé aux différentes méthodes appelées.

Le score contient donc dans mon cas une exception qui contient une fermeture, puisque c'est l'un des arguments de foo::__set().

Or, PHP refuse catégoriquement de sérialiser les fermetures.

Si vous essayez, vous obtiendrez le message suivant :

PHP Fatal error:  Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' in /usr/home/fch/tmp/closure.php:5

Du coup, dans ce cas, ma communication inter-processus ne peut plus fonctionner, vu que :

  1. la sérialisation du score entraîne la sérialisation de l'exception
  2. la sérialisation de l'exception entraîne celle de la fermeture, via la pile d'exécution contenue dans l'exception
  3. Une exception est généré PHP et écrite sur la sortie standard, que le processus père n'est pas capable d'interpréter.

La solution à ce problème ?

J'ai bien pensé à utiliser l'interface serializable au niveau de mon score, mais la pile d'exécution contient un objet de la classe \closure, qui ne peut être redéfinie.

De plus, il n'est pas non plus possible de modifier la pile d'exécution d'une exception puisque cette dernière est en lecture seule et que la propriété correspondante est privée.

La seule solution consiste donc à ne pas utiliser d'exception si une fermeture est susceptible de se retrouver dans sa pile d'exécution et qu'elle est susceptible d'être sérialisée.

Autant dire que cette solution est inapplicable dans les faits, puisqu'il faudrait au minimum être voyant extralucide pour pouvoir dire comment sera utilisé dans le futur un code conçu maintenant.

Il est également possible d'utiliser trigger_error() pour générer une erreur et non une exception, mais suivant le contexte, ce n'est pas forcément le comportement voulu.

La meilleure solution est donc que le fonctionnement de PHP soit modifié afin qu'il n'empêche plus la sérialisation d'objet contenant des fermetures.