PHP 7.1 : créer une Closure à partir d'un appelable
13 septembre 2016 —This post is also available in English.
Ceci est le septième article d’une série à propos de PHP 7.1.
Historiquement, un callable a souvent été manipulé, en PHP, sous la forme d’une chaine de caractères. Par exemple, on utilise la fonction array_map()
pour appeler le callable 'trim'
sur tous les éléments d’un tableau.
Cette approche souffre d’un défaut majeur : la validité de l’appelable n’est déterminée que lors de son appel !
Par exemple, considérons la portion de code suivante :
function ma_fonction($param)
{
printf("%s(%s)\n", __FUNCTION__, $param);
}
// Pas de problème détecté ici, malgré la faute de frappe
$nom = 'ma_fonctlon';
// Ici, de nombreuses lignes de code
// et encore plein d’autres
// C’est seulement ici qu’on a un problème :
// Warning: call_user_func() expects parameter 1 to be a valid callback, function 'ma_fonctlon' not found or invalid function name
call_user_func($nom, 42);
La faute de frappe dans le nom de la fonction de rappel n’est pas détectée à l’endroit où elle est commise… mais seulement plusieurs lignes plus loin, lorsque nous essayons de l’exécuter.
Sur un exemple réel, qui s’étalerait sur plusieurs dizaines ou centaines de fichiers, peut-être même par le biais de quelques fichiers de configuration, je vous laisse imaginer l’enfer en termes de débogage ! Peut-être même l’avez-vous déjà vécu ?
PHP 7.1 répond à cette problématique en introduisant une nouvelle méthode Closure::fromCallable()
. Elle prend en paramètre un appelable, le valide, puis retourne une Closure
le référençant. C’est cette Closure
que vous manipulerez par la suite.
Nous pouvons donc réécrire l’exemple ci-dessus en tirant profit de cette fonctionnalité. Une exception instance de TypeError
est levée si l’appelable spécifié n’est pas valide :
function ma_fonction() {
var_dump(__FUNCTION__);
}
$closure = Closure::fromCallable('ma_fonction');
$closure(); // string(11) "ma_fonction"
$closure = Closure::fromCallable('plop');
// TypeError: Failed to create closure from callable: function 'plop' not found or invalid function name
Avantage : en cas d’erreur de frappe dans le nom de la fonction de rappel, elle est détectée immédiatement – sans avoir à attendre son exécution :
function ma_fonction($param)
{
printf("%s(%s)\n", __FUNCTION__, $param);
}
// L’erreur est immédiatement détectée
// Fatal error: Uncaught TypeError: Failed to create closure from callable: function 'ma_fonctlon' not found or invalid function name
$callable = Closure::fromCallable('ma_fonctlon');
// Ici, de nombreuses lignes de code
// et encore plein d’autres
// Pas de problème ici : sauf erreur plus haut,
// $callable est forcément valide
call_user_func($callable, 42);
Le problème causé par l’absence de validation des callables lors de leur déclaration se posait également pour d’autres types d’appelables, comme ceux désignant une méthode d’un objet :
class MaClasse
{
protected $data;
public function __construct($param)
{
$this->data = $param;
}
public function maMethode()
{
printf("%s() -> %s\n", __METHOD__, $this->data);
}
}
// Pas d’erreur levée par PHP ici, alors que c’est sur cette ligne
// qu’on a fait une typo !
$callable = [new MaClasse(42), 'maMehtode'];
// Pas non plus d’erreur ici
call_callable($callable);
function call_callable($callable)
{
// C’est seulement ici qu’on a une erreur !
// Fatal error: Uncaught Error: Call to undefined method MaClasse::maMehtode()
$callable();
}
Heureusement, nous pouvons également, avec PHP 7.1, utiliser Closure::fromCallable()
dans ce cas. Nous bénéficierons ici aussi de la validation de l’appelable lors de la création de la Closure
, souvent bien avant son exécution :
class MaClasse
{
protected $data;
public function __construct($param)
{
$this->data = $param;
}
public function maMethode()
{
printf("%s() -> %s\n", __METHOD__, $this->data);
}
}
// L’erreur est immédiatement détectée
// Fatal error: Uncaught TypeError: Failed to create closure from callable: class 'MaClasse' does not have a method 'maMehtode'
$callable = Closure::fromCallable([new MaClasse(42), 'maMehtode']);
// Pas de problème ici : $callable est forcément valide
call_callable($callable);
// Bonus : on peut type-declarer
function call_callable(callable $callable)
{
$callable();
}
Bref, voici une nouvelle méthode qui va nous permettre de valider les appelables au plus tôt, facilitant ainsi la détection et la gestion d’erreur !
‣ La RFC : Closure from callable function