Jak zaimplementować wywołanie zwrotne w PHP?


Odpowiedzi:


173

Podręcznik używa terminów „callback” i „callable” zamiennie, jednak „callback” tradycyjnie odnosi się do wartości ciągu lub tablicy, która działa jak wskaźnik funkcji , odwołując się do funkcji lub metody klasy do przyszłego wywołania. Pozwoliło to na niektóre elementy programowania funkcjonalnego od PHP 4. Smaki są następujące:

$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');

Jest to bezpieczny sposób na używanie ogólnie dostępnych wartości:

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);
}

Nowoczesne wersje PHP pozwalają wywoływać trzy pierwsze formaty powyżej bezpośrednio jako $cb(). call_user_funci call_user_func_arraywesprzyj wszystkie powyższe.

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

Uwagi / zastrzeżenia:

  1. Jeśli funkcja / klasa ma przestrzeń nazw, ciąg musi zawierać pełną nazwę. Na przykład['Vendor\Package\Foo', 'method']
  2. call_user_funcnie obsługuje przekazywania obiektów niebędących obiektami przez referencję, więc możesz użyć call_user_func_arraylub, w późniejszych wersjach PHP, zapisać wywołanie zwrotne w zmiennej var i użyć bezpośredniej składni:$cb() ;
  3. Obiekty z __invoke()metodą (w tym funkcje anonimowe) należą do kategorii „wywoływalne” i mogą być używane w ten sam sposób, ale ja osobiście nie kojarzę ich ze starszym terminem „wywołanie zwrotne”.
  4. Dziedzictwo create_function()tworzy funkcję globalną i zwraca jej nazwę. Jest to opakowanie dla eval()i zamiast tego należy użyć funkcji anonimowych.

Rzeczywiście, użycie tej funkcji jest właściwym sposobem na zrobienie tego. Podczas korzystania ze zmiennej, a następnie po prostu jej wywoływania, jak sugeruje zaakceptowana odpowiedź, jest fajna, jest brzydka i nie da się dobrze skalować z kodem.
icco

4
Zmieniono zaakceptowaną odpowiedź. Zgadzając się z komentarzami, jest to świetna odpowiedź.
Nick Stinemon,

Przydałoby się czytelnikom pochodzącym z programowania w języku Python wskazanie w odpowiedzi, że 'someGlobalFunction'rzeczywiście jest to zdefiniowana funkcja.
TMOTTM

1
Począwszy od PHP 5.3, istnieją zamknięcia, patrz odpowiedź Bart van Heukelom . Jest o wiele prostszy i „standardowy” niż cały ten stary bałagan.
naprawde

Tak, w mojej odpowiedzi wspominam o funkcjach anonimowych, ale OP poprosił o „callback” (w 2008 roku), a te starsze style callbacków są nadal bardzo popularne w tonach baz kodu PHP.
Steve Clay

74

W PHP 5.3 możesz teraz to zrobić:

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

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

Wreszcie fajny sposób na zrobienie tego. Świetny dodatek do PHP, ponieważ wywołania zwrotne są niesamowite.


1
To musi być najlepsza odpowiedź.
GROVER.

30

Implementacja wywołania zwrotnego odbywa się w ten sposób

// 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');

Wyświetla: Dane to: to moje dane


21
O mój Boże. Czy to standard? To straszne!
Nick Retallack,

Istnieje kilka innych sposobów, jak to pokazano powyżej. Naprawdę myślałem tak samo.
Nick Stinemon,

3
@Nick Retallack, nie rozumiem, co jest w tym tak okropnego. W przypadku języków, które znam, takich jak JavaScript i C #, wszystkie one mogą ustrukturyzować swoją funkcję zwrotną w taki sposób. Pochodzę z JavaScirpt i C #, tak naprawdę nie jestem przyzwyczajony do call_user_func (). Czuję, że muszę się dostosować do PHP, zamiast na odwrót.
Antony

4
@Antony Sprzeciwiłem się temu, że ciągi są wskaźnikami funkcji w tym języku. Zamieściłem ten komentarz trzy lata temu, więc jestem już do tego przyzwyczajony, ale myślę, że PHP jest jedynym znanym mi językiem (innym niż skryptowanie powłoki), w którym tak jest.
Nick Retallack,

1
@Antony Wolę używać tej samej składni, której używam w javascript. Więc nawet nie rozumiem, dlaczego ludzie chcą korzystać, call_user_func()gdy mają składnię, która umożliwia im dynamiczne wywoływanie funkcji i wykonywanie połączeń zwrotnych. Zgadzam się z Tobą!
botenvouwer

9

Jedną z fajnych sztuczek, które ostatnio znalazłem, jest użycie PHP create_function()do stworzenia anonimowej / lambda funkcji do jednorazowego użycia. Jest to użyteczne dla funkcji PHP, takich jak array_map(), preg_replace_callback()lub usort(), że używają wywołań zwrotnych do przetwarzania niestandardowej. Wygląda prawie tak, jakby eval()działał pod przykryciem, ale nadal jest to przyjemny funkcjonalny sposób używania PHP.


6
Niestety, śmieciarz nie działa zbyt dobrze z tym konstruktem, powodując potencjalne wycieki pamięci. Jeśli zależy Ci na wydajności, unikaj funkcji create_function ().
fuxia

1
Auć. Dzięki za zgłoszenie się.
yukondude

Czy mógłbyś zaktualizować odpowiedź w wersji PHP 7.4 (funkcje strzałek) i dodać ostrzeżenie o przestarzałym create_function()?
Dharman


7

Będziesz chciał zweryfikować, czy Twoje połączenie jest prawidłowe. Na przykład w przypadku określonej funkcji będziesz chciał sprawdzić i sprawdzić, czy funkcja istnieje:

function doIt($callback) {
    if(function_exists($callback)) {
        $callback();
    } else {
        // some error handling
    }
}

Co jeśli wywołanie zwrotne nie jest funkcją, ale tablicą przechowującą obiekt i metodę?
d -_- b

3
a raczejis_callable( $callback )
Roko C. Buljan

1
Oba są dobrymi sugestiami - Konieczne będzie sprawdzenie własnej implementacji sposobu wykonywania oddzwaniania. Miałem nadzieję ostrzec przed wezwaniem czegoś, co nie istniało, powodując śmierć.
Uczyniłem

5

create_functionnie działało dla mnie w klasie. Musiałem użyć 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!" );
        }
    }

}

Następnie, aby użyć:

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

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

[Edytuj] Dodano brakujący nawias. Ponadto dodano deklarację oddzwonienia, wolę to w ten sposób.


1
Czy klasa Dispatcher nie wymaga atrybutu, aby $ this-> callback = $ callback działał?
James P.

@james poulson: PHP jest językiem dynamicznym, więc działa. Ale byłem leniwy. Zwykle deklaruję właściwości, ułatwia wszystkim życie. Twoje pytanie sprawiło, że ponownie spojrzałem na ten kod i zauważyłem błąd składniowy. Dzięki
goliatone,

Nie wiedziałem, że to możliwe. Dziękuję za odpowiedź :) .
James P.

Czy nie byłoby bezpieczniej zadeklarować funkcję do_callback przed utworzeniem obiektu Dispatcher i wywołaniem metody asynchronicznej?
ejectamenta

3

Kulę się za każdym razem, gdy używam create_function()w php.

Parametry to ciąg rozdzielony przecinkami, całe ciało funkcji w ciągu ... Argh ... Myślę, że nie mogliby uczynić go brzydszym, nawet gdyby próbowali.

Niestety jest to jedyny wybór, gdy utworzenie nazwanej funkcji nie jest warte kłopotu.


1
I oczywiście jest to eval string wykonawczy, więc nie jest sprawdzany pod kątem poprawnej składni ani niczego innego w czasie kompilacji.
hobbs

Ta odpowiedź była nieaktualna przez ostatnie 2 lata. create_function()jest teraz przestarzałe i nie należy go używać.
Dharman

2

Dla tych, którym nie zależy na zerwaniu kompatybilności z PHP < 5.4, sugeruję użycie podpowiedzi typu, aby uzyskać czystszą implementację.

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"

Ostrzeżenie create_function() zostało DEPRECATED od PHP 7.2.0. Poleganie na tej funkcji jest wysoce odradzane.
Dharman
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.