Jak zaimplementować wywołanie zwrotne w PHP?

181

Jak napisane są zwrotne w PHP?

Nick Stinemon
źródło
1
Połączę kolejne pytanie z tym, ponieważ próbowałem ogłosić zamknięcie.
akinuri

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.
Steve Clay
źródło
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.

Bart van Heukelom
źródło
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

Nick Stinemon
źródło
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.

yukondude
źródło
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
    }
}
SeanDowney
źródło
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.

Goliaton
źródło
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.

Poklepać
źródło
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"
Kendall Hopkins
źródło
Ostrzeżenie create_function() zostało DEPRECATED od PHP 7.2.0. Poleganie na tej funkcji jest wysoce odradzane.
Dharman