Como implemento um retorno de chamada em PHP?

181

Como os retornos de chamada são escritos em PHP?

Nick Stinemates
fonte
1
Vou vincular outra pergunta a essa, porque eu estava tentando encerrar.
akinuri

Respostas:

173

O manual usa os termos "retorno de chamada" e "retornável" de forma intercambiável, no entanto, "retorno de chamada" tradicionalmente se refere a um valor de cadeia ou matriz que age como um ponteiro de função , referenciando um método de função ou classe para invocação futura. Isso permitiu alguns elementos de programação funcional desde o PHP 4. Os sabores são:

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

Esta é uma maneira segura de usar valores de chamada em geral:

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

As versões modernas do PHP permitem que os três primeiros formatos acima sejam chamados diretamente como $cb(). call_user_funce call_user_func_arrayapoiar todos os itens acima.

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

Notas / Advertências:

  1. Se a função / classe estiver com namespace, a sequência deverá conter o nome completo. Por exemplo['Vendor\Package\Foo', 'method']
  2. call_user_funcnão suporta passando não-objetos por referência, para que você pode usar call_user_func_arrayou, em versões do PHP mais tarde, guarde o retorno a um var e utilizar a sintaxe direta: $cb();
  3. Objetos com um __invoke()método (incluindo funções anônimas) se enquadram na categoria "solicitável" e podem ser usados ​​da mesma maneira, mas pessoalmente não os associo ao termo "retorno de chamada" herdado.
  4. O legado create_function()cria uma função global e retorna seu nome. É um invólucro para eval()e funções anônimas devem ser usadas.
Steve Clay
fonte
De fato, o uso da função é a maneira correta de fazê-lo. Ao usar uma variável e depois chamá-la, como sugerido na resposta aceita, é legal, é feio e não se adapta bem ao código.
Icco 30/08/09
4
Resposta aceita alterada. Concordo com os comentários, esta é uma ótima resposta.
9609 Nick Stinemates
Seria útil para os leitores provenientes da programação Python apontar na resposta que 'someGlobalFunction'realmente é uma função definida.
TMOTTM
1
No PHP 5.3, existem encerramentos, veja a resposta de Bart van Heukelom . É muito mais simples e "padrão" do que toda essa bagunça herdada.
reallynice
Sim, mencionei funções anônimas na minha resposta, mas o OP solicitou "retorno de chamada" (em 2008) e esses retornos de chamada no estilo antigo ainda são muito utilizados em toneladas de bases de código PHP.
23415 Steve
74

Com o PHP 5.3, agora você pode fazer o seguinte:

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

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

Finalmente, uma boa maneira de fazê-lo. Um ótimo complemento para o PHP, porque os retornos de chamada são impressionantes.

Bart van Heukelom
fonte
1
Essa precisa ser a resposta principal.
GROVER.
30

A implementação de um retorno de chamada é feita assim

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

Exibe: Os dados são: estes são meus dados

Nick Stinemates
fonte
21
Oh meu Deus. Esse é o padrão? Isso é terrível!
Nick Retallack
Existem algumas outras maneiras de fazer isso, como mostrado acima. Eu realmente pensei o mesmo.
Nick Stinemates
3
@ Nick Retallack, não vejo o que há de tão horrível nisso. Para os idiomas que eu conheço, como JavaScript e C #, todos eles podem estruturar sua função de retorno de chamada nesse padrão. Vindo de JavaScirpt e C #, eu realmente não estou acostumado a call_user_func (). Isso me faz sentir que tenho que me adaptar ao PHP, e não o contrário.
Antony
4
@Antony Eu estava objetando ao fato de que strings são indicadores de função nessa linguagem. Eu publiquei esse comentário há três anos, por isso estou bastante acostumado a ele agora, mas acho que o PHP é a única linguagem que conheço (além do shell scripting) onde esse é o caso.
Nick Retallack
1
@Antony Prefiro usar a mesma sintaxe que uso em javascript. Então, eu nem entendo por que as pessoas querem usar call_user_func()Quando possuem uma sintaxe que lhes permite chamar dinamicamente funções e fazer retornos de chamada. Eu concordo com você!
precisa saber é o seguinte
9

Um truque bacana que eu descobri recentemente é usar o PHP create_function()para criar uma função anônima / lambda para uso único. É útil para funções PHP, como array_map(), preg_replace_callback(), ou usort()que as chamadas de retorno de uso para processamento personalizado. Parece muito com um eval()arquivo oculto, mas ainda é uma boa maneira de usar o PHP em estilo funcional.

yukondude
fonte
6
Infelizmente, o coletor de lixo não funciona muito bem com essa construção produzindo possíveis vazamentos de memória. Se você quer desempenho, evite a função create_function ().
Fuxia
1
Ai. Obrigado pelo aviso.
yukondude
Você se importaria de atualizar a resposta com a versão do PHP 7.4 (funções de seta) e adicionar um aviso sobre obsoleto create_function()?
Dharman
7

Você deseja verificar se a sua chamada é válida. Por exemplo, no caso de uma função específica, você deseja verificar e ver se a função existe:

function doIt($callback) {
    if(function_exists($callback)) {
        $callback();
    } else {
        // some error handling
    }
}
SeanDowney
fonte
E se o retorno de chamada não for uma função, mas uma matriz que contenha objeto e método?
precisa
3
ou melhor #is_callable( $callback )
Roko C. Buljan 30/03
1
Ambas são boas sugestões - Será necessário verificar sua própria implementação específica de como você faz um retorno de chamada. Eu esperava advertir contra chamar algo que não existia causando uma morte fatal. Eu fiz a resposta mais geral
SeanDowney
5

create_functionnão funcionou para mim dentro de uma classe. Eu tive que usar 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!" );
        }
    }

}

Então, para usar:

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

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

[Editar] Adicionado um parêntese ausente. Além disso, adicionei a declaração de retorno de chamada, prefiro assim.

goliatone
fonte
1
A classe Dispatcher não requer um atributo para que $ this-> callback = $ callback funcione?
James P.
@ Poulson James: PHP é uma linguagem dinâmica, por isso funciona. Mas eu estava sendo preguiçoso. Eu geralmente declaro propriedades, facilita a vida de todos. Sua pergunta me fez olhar para esse código novamente e detectar um erro de sintaxe, tu. Obrigado
goliatone
Eu não sabia que isso era possível. Obrigado pela resposta :).
James P.
Não seria mais seguro declarar a função do_callback antes de criar o objeto Dispatcher e chamar o método assíncrono?
ejectamenta
3

Eu me encolho toda vez que uso create_function() em php.

Parâmetros são uma string separada por coma, todo o corpo da função em uma string ... Argh ... Eu acho que eles não poderiam ter tornado a coisa mais feia, mesmo que tentassem.

Infelizmente, é a única opção ao criar uma função nomeada que não vale a pena.

Pat
fonte
1
E, é claro, é a avaliação de sequência de tempo de execução, para que não seja verificada a sintaxe válida ou qualquer outra coisa em tempo de compilação.
21139 hobbs
Esta resposta está desatualizada nos últimos 2 anos. create_function()agora está obsoleto e não deve ser usado.
Dharman 01/03
2

Para aqueles que não se importam em quebrar a compatibilidade com o PHP < 5.4, sugiro usar dicas de tipo para tornar uma implementação mais limpa.

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
fonte
O aviso create_function() foi descontinuado a partir do PHP 7.2.0. Confiar nessa função é altamente desencorajado.
Dharman 01/03