Comment implémenter un callback en PHP?

187

Comment les callbacks sont-ils écrits en PHP?

Nick Stinemates
la source
1
Je vais lier une autre question à celle-ci, parce que j'essayais d'appeler une fermeture.
akinuri

Réponses:

173

Le manuel utilise les termes "callback" et "callable" de manière interchangeable, cependant, "callback" se réfère traditionnellement à une valeur de chaîne ou de tableau qui agit comme un pointeur de fonction , référençant une fonction ou une méthode de classe pour un appel futur. Cela a permis certains éléments de programmation fonctionnelle depuis PHP 4. Les saveurs sont:

$cb1 = 'someGlobalFunction';
$cb2 = ['ClassName', 'someStaticMethod'];
$cb3 = [$object, 'somePublicMethod'];

// this syntax is callable since PHP 5.2.3 but a string containing it
// cannot be called directly
$cb2 = 'ClassName::someStaticMethod';
$cb2(); // fatal error

// legacy syntax for PHP 4
$cb3 = array(&$object, 'somePublicMethod');

C'est un moyen sûr d'utiliser des valeurs appelables en général:

if (is_callable($cb2)) {
    // Autoloading will be invoked to load the class "ClassName" if it's not
    // yet defined, and PHP will check that the class has a method
    // "someStaticMethod". Note that is_callable() will NOT verify that the
    // method can safely be executed in static context.

    $returnValue = call_user_func($cb2, $arg1, $arg2);
}

Les versions modernes de PHP permettent aux trois premiers formats ci-dessus d'être appelés directement en tant que $cb(). call_user_funcet call_user_func_arraysoutenez tout ce qui précède.

Voir: http://php.net/manual/en/language.types.callable.php

Remarques / mises en garde:

  1. Si la fonction / classe a un espace de noms, la chaîne doit contenir le nom complet. Par exemple['Vendor\Package\Foo', 'method']
  2. call_user_funcne supporte pas le passage des non-objets par référence, vous pouvez utiliser call_user_func_arrayou, dans les versions PHP plus tard, enregistrez le rappel à un var et utiliser la syntaxe directe: $cb();
  3. Les objets avec une __invoke()méthode (y compris les fonctions anonymes) entrent dans la catégorie "appelable" et peuvent être utilisés de la même manière, mais personnellement je ne les associe pas au terme "rappel" hérité.
  4. L'héritage create_function()crée une fonction globale et renvoie son nom. C'est un wrapper pour eval()et des fonctions anonymes devraient être utilisées à la place.
Steve Clay
la source
En effet, utiliser la fonction est la bonne façon de le faire. En utilisant une variable et en l'appelant, comme suggéré dans la réponse acceptée, c'est cool, c'est moche et ne s'adapte pas bien au code.
icco
4
Réponse acceptée modifiée. D'accord avec les commentaires, c'est une excellente réponse.
Nick Stinemates
Il serait utile aux lecteurs issus de la programmation Python de souligner dans la réponse qu'il 'someGlobalFunction's'agit bien d'une fonction définie.
TMOTTM
1
Depuis PHP 5.3, il y a des fermetures, voir la réponse de Bart van Heukelom . C'est beaucoup plus simple et "standard" que tout ce désordre hérité.
reallynice
Oui, je mentionne des fonctions anonymes dans ma réponse, mais l'OP a demandé un «rappel» (en 2008) et ces rappels de style ancien sont toujours très utilisés dans des tonnes de bases de code PHP.
Steve Clay
75

Avec PHP 5.3, vous pouvez maintenant faire ceci:

function doIt($callback) { $callback(); }

doIt(function() {
    // this will be done
});

Enfin une belle façon de le faire. Un excellent ajout à PHP, car les rappels sont géniaux.

Bart van Heukelom
la source
1
Cela doit être la meilleure réponse.
GROVER.
30

L'implémentation d'un callback se fait comme ceci

// This function uses a callback function. 
function doIt($callback) 
{ 
    $data = "this is my data";
    $callback($data); 
} 


// This is a sample callback function for doIt(). 
function myCallback($data) 
{ 
    print 'Data is: ' .  $data .  "\n"; 
} 


// Call doIt() and pass our sample callback function's name. 
doIt('myCallback');

Affiche: Les données sont: ce sont mes données

Nick Stinemates
la source
21
Oh mon Dieu. Est-ce la norme? C'est terrible!
Nick Retallack
Il existe plusieurs autres façons de le faire, comme indiqué ci-dessus. J'ai vraiment pensé la même chose.
Nick Stinemates
3
@Nick Retallack, je ne vois pas ce qu'il y a de si horrible à ce sujet. Pour les langages que je connais, tels que JavaScript et C #, ils peuvent tous structurer leur fonction de rappel dans un tel modèle. Venant de JavaScirpt et C #, je ne suis vraiment pas habitué à call_user_func (). Cela me donne l'impression de devoir m'adapter à PHP, au lieu de l'inverse.
Antony
4
@Antony Je m'opposais au fait que les chaînes sont des pointeurs de fonction dans ce langage. J'ai posté ce commentaire il y a trois ans, donc j'y suis assez habitué maintenant, mais je pense que PHP est le seul langage que je connaisse (autre que le script shell) où c'est le cas.
Nick Retallack
1
@Antony Je préfère utiliser la même syntaxe que j'utilise en javascript. Donc je ne comprends même pas pourquoi les gens veulent utiliser call_user_func()Quand ils ont une syntaxe qui leur permet d'appeler dynamiquement des fonctions et de faire des rappels. Je suis d'accord avec toi!
botenvouwer
9

Une astuce astucieuse que j'ai récemment trouvée est d'utiliser PHP create_function()pour créer une fonction anonyme / lambda pour une utilisation ponctuelle. Il est utile pour les fonctions PHP comme array_map(), preg_replace_callback()ou usort()que callbacks utilisation pour le traitement personnalisé. Cela ressemble à peu près à un eval()sous les couvertures, mais c'est toujours une belle façon fonctionnelle d'utiliser PHP.

Yukondude
la source
6
Malheureusement, le garbage collector ne joue pas très bien avec cette construction produisant des fuites de mémoire potentielles. Si vous êtes à la recherche de performances, évitez create_function ().
fuxia
Pourriez-vous mettre à jour la réponse avec la version PHP 7.4 (fonctions fléchées) et ajouter un avertissement concernant les obsolètes create_function()?
Dharman
7

Vous voudrez vérifier quel que soit votre appel est valide. Par exemple, dans le cas d'une fonction spécifique, vous voudrez vérifier et voir si la fonction existe:

function doIt($callback) {
    if(function_exists($callback)) {
        $callback();
    } else {
        // some error handling
    }
}
SeanDowney
la source
Et si le rappel n'est pas une fonction, mais un tableau contenant un objet et une méthode?
d -_- b
3
ou plutôtis_callable( $callback )
Roko C. Buljan
1
Les deux sont de bonnes suggestions - Il sera nécessaire de vérifier votre propre implémentation particulière de la façon dont vous effectuez un rappel. J'espérais mettre en garde contre le fait d'appeler quelque chose qui n'existait pas causant un décès. J'ai rendu la réponse plus générale
SeanDowney
5

create_functionn'a pas fonctionné pour moi dans une classe. Je devais utiliser call_user_func.

<?php

class Dispatcher {
    //Added explicit callback declaration.
    var $callback;

    public function Dispatcher( $callback ){
         $this->callback = $callback;
    }

    public function asynchronous_method(){
       //do asynch stuff, like fwrite...then, fire callback.
       if ( isset( $this->callback ) ) {
            if (function_exists( $this->callback )) call_user_func( $this->callback, "File done!" );
        }
    }

}

Ensuite, pour utiliser:

<?php 
include_once('Dispatcher.php');
$d = new Dispatcher( 'do_callback' );
$d->asynchronous_method();

function do_callback( $data ){
   print 'Data is: ' .  $data .  "\n";
}
?>

[Edit] Ajout d'une parenthèse manquante. De plus, j'ai ajouté la déclaration de rappel, je la préfère ainsi.

goliatone
la source
1
La classe Dispatcher ne nécessite-t-elle pas un attribut pour que $ this-> callback = $ callback fonctionne?
James P.
@james poulson: PHP est un langage dynamique, donc ça marche. Mais j'étais paresseux. Je déclare généralement des propriétés, facilite la vie de tout le monde. Votre question m'a fait revoir ce code et repérer une erreur de syntaxe, toi. Merci
goliatone
Je ne savais pas que c'était possible. Merci d'avoir répondu :) .
James P.
Ne serait-il pas plus sûr de déclarer la fonction do_callback avant de créer l'objet Dispatcher et d'appeler la méthode async?
ejectamenta
3

Je grince des dents à chaque fois que j'utilise create_function()en php.

Les paramètres sont une chaîne séparée par des virgules, tout le corps de la fonction dans une chaîne ... Argh ... Je pense qu'ils n'auraient pas pu le rendre plus laid même s'ils avaient essayé.

Malheureusement, c'est le seul choix lorsque la création d'une fonction nommée ne vaut pas la peine.

Tapoter
la source
1
Et, bien sûr, c'est eval de chaîne d'exécution, donc il n'est pas vérifié pour la syntaxe valide ou quoi que ce soit d'autre au moment de la compilation.
hobbs
Cette réponse est dépassée depuis 2 ans. create_function()est désormais obsolète et ne doit pas être utilisé.
Dharman
2

Pour ceux qui ne se soucient pas de rompre la compatibilité avec PHP < 5.4, je suggère d'utiliser l'indication de type pour créer une implémentation plus propre.

function call_with_hello_and_append_world( callable $callback )
{
     // No need to check $closure because of the type hint
     return $callback( "hello" )."world";
}

function append_space( $string )
{
     return $string." ";
}

$output1 = call_with_hello_and_append_world( function( $string ) { return $string." "; } );
var_dump( $output1 ); // string(11) "hello world"

$output2 = call_with_hello_and_append_world( "append_space" );
var_dump( $output2 ); // string(11) "hello world"

$old_lambda = create_function( '$string', 'return $string." ";' );
$output3 = call_with_hello_and_append_world( $old_lambda );
var_dump( $output3 ); // string(11) "hello world"
Kendall Hopkins
la source
L'avertissement create_function() est obsolète depuis PHP 7.2.0. Il est fortement déconseillé de se fier à cette fonction.
Dharman