Fechamento de chamada atribuído à propriedade do objeto diretamente

109

Gostaria de poder chamar um encerramento que atribuo à propriedade de um objeto diretamente, sem reatribuir o encerramento a uma variável e, em seguida, chamá-lo. Isso é possível?

O código abaixo não funciona e faz com que Fatal error: Call to undefined method stdClass::callback() .

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
Kendall Hopkins
fonte
1
Isso é exatamente o que você precisa: github.com/ptrofimov/jslikeobject Ainda mais: você pode usar $ this dentro de encerramentos e usar herança. Somente PHP> = 5.4!
Renato Cuccinotto

Respostas:

106

A partir do PHP7, você pode fazer

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

ou use Closure :: call () , embora isso não funcione em a StdClass.


Antes do PHP7, você teria que implementar o __callmétodo mágico para interceptar a chamada e invocar o retorno de chamada (o que não é possível StdClass, é claro, porque você não pode adicionar o __callmétodo)

class Foo
{
    public function __call($method, $args)
    {
        if(is_callable(array($this, $method))) {
            return call_user_func_array($this->$method, $args);
        }
        // else throw exception
    }
}

$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');

Observe que você não pode fazer

return call_user_func_array(array($this, $method), $args);

no __callcorpo, porque isso dispararia __callem um loop infinito.

Gordon
fonte
2
Algumas vezes você achará esta sintaxe útil - call_user_func_array ($ this -> $ property, $ args); quando se trata de propriedade de classe que pode ser chamada, não de um método.
Nikita Gopkalo
104

Você pode fazer isso chamando __invoke na closure, pois esse é o método mágico que os objetos usam para se comportar como funções:

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

É claro que isso não funcionará se o callback for um array ou string (que também podem ser callbacks válidos em PHP) - apenas para closures e outros objetos com comportamento __invoke.

Brilliand
fonte
3
@marcioAlmada é muito feio.
Mahn
1
@Mahn acho que é mais explícito do que a resposta aceita. Explícito é melhor neste caso. Se você realmente se preocupa com uma solução "fofa", call_user_func($obj->callback)não é tão ruim assim.
marcio
Mas também call_user_funcfunciona com cordas e isso nem sempre é conveniente
Gherman
2
@cerebriforme semanticamente não faz sentido ter que ser feito $obj->callback->__invoke();quando se esperava que fosse $obj->callback(). É apenas uma questão de consistência.
Mahn
3
@Mahn: Concedido, não é consistente. No entanto, consistência nunca foi realmente o forte do PHP. :) Mas eu acho que ele faz faz sentido, quando se considera o objeto natureza: $obj->callback instanceof \Closure.
bispo de
24

A partir do PHP 7, você pode fazer o seguinte:

($obj->callback)();
Korikulum
fonte
Tanto senso comum, mas é puramente durão. O grande poder do PHP7!
tfont
10

Desde o PHP 7, um encerramento pode ser chamado usando o call()método:

$obj->callback->call($obj);

Já que o PHP 7 também é possível executar operações em (...)expressões arbitrárias (conforme explicado por Korikulum ):

($obj->callback)();

Outras abordagens comuns do PHP 5 são:

  • usando o método mágico __invoke()(conforme explicado por Brilliand )

    $obj->callback->__invoke();
  • usando a call_user_func()função

    call_user_func($obj->callback);
  • usando uma variável intermediária em uma expressão

    ($_ = $obj->callback) && $_();

Cada caminho tem seus prós e contras, mas a solução mais radical e definitiva ainda é a apresentada por Gordon .

class stdKlass
{
    public function __call($method, $arguments)
    {
        // is_callable([$this, $method])
        //   returns always true when __call() is defined.

        // is_callable($this->$method)
        //   triggers a "PHP Notice: Undefined property" in case of missing property.

        if (isset($this->$method) && is_callable($this->$method)) {
            return call_user_func($this->$method, ...$arguments);
        }

        // throw exception
    }
}

$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();
Daniele Orlando
fonte
7

Parece ser possível usar call_user_func().

call_user_func($obj->callback);

não é elegante, entretanto ... O que @Gordon diz é provavelmente o único caminho a percorrer.

Pekka
fonte
7

Bem, se você realmente insiste. Outra solução alternativa seria:

$obj = new ArrayObject(array(),2);

$obj->callback = function() {
    print "HelloWorld!";
};

$obj['callback']();

Mas essa não é a sintaxe mais agradável.

No entanto, o analisador PHP sempre trata T_OBJECT_OPERATOR, IDENTIFIER, (como chamada de método. Parece não haver uma solução alternativa para ->evitar a tabela de métodos e acessar os atributos.

mario
fonte
7

Eu sei que isso é antigo, mas acho que o Traits resolve bem esse problema se você estiver usando PHP 5.4+

Primeiro, crie uma característica que torne as propriedades chamáveis:

trait CallableProperty {
    public function __call($method, $args) {
        if (property_exists($this, $method) && is_callable($this->$method)) {
            return call_user_func_array($this->$method, $args);
        }
    }
}

Então, você pode usar essa característica em suas aulas:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Agora, você pode definir propriedades por meio de funções anônimas e chamá-las diretamente:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
SteveK
fonte
Uau :) Isso é muito mais elegante do que o que eu estava tentando fazer. Minha única pergunta é a mesma para os elementos conectores da torneira quente / fria britânica: POR QUE ainda não está embutido ??
dkellner
Obrigado :). Provavelmente não está integrado devido à ambiguidade que cria. Imagine no meu exemplo, se houvesse realmente uma função chamada "adicionar" e uma propriedade chamada "adicionar". A presença do parêntese diz ao PHP para procurar uma função com aquele nome.
SteveK
2

bem, deve-se enfatizar que armazenar o fechamento em uma variável e chamar a variável é na verdade (estranhamente) mais rápido, dependendo do valor da chamada, torna-se bastante, com xdebug (medição muito precisa), estamos falando 1,5 (o fator, usando uma variável, em vez de chamar diretamente o __invoke. Então, em vez disso, apenas armazene o encerramento em uma variável e chame-a.

Kmtdk
fonte
2

Atualizada:

$obj = new stdClass();
$obj->callback = function() {
     print "HelloWorld!";
};

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

$callback = $obj->callback;  
$callback();
M Rostami
fonte
Você tentou isso? Não funciona. Call to undefined method AnyObject::callback()(A classe AnyObject existe, é claro.)
Kontrollfreak de
1

Aqui está outra alternativa com base na resposta aceita, mas estendendo stdClass diretamente:

class stdClassExt extends stdClass {
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

Exemplo de uso:

$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();

Você provavelmente está melhor usando call_user_funcou __invokeembora.

Mahn
fonte
0

Se você estiver usando PHP 5.4 ou superior, poderá vincular um chamável ao escopo do seu objeto para invocar o comportamento personalizado. Por exemplo, se você tivesse a seguinte configuração ..

function run_method($object, Closure $method)
{
    $prop = uniqid();
    $object->$prop = \Closure::bind($method, $object, $object);
    $object->$prop->__invoke();
    unset($object->$prop);
}

E você estava operando em uma classe assim ..

class Foo
{
    private $value;
    public function getValue()
    {
        return $this->value;
    }
}

Você pode executar sua própria lógica como se estivesse operando dentro do escopo de seu objeto

$foo = new Foo();
run_method($foo, function(){
    $this->value = 'something else';
});

echo $foo->getValue(); // prints "something else"
Lee Davis
fonte
0

Noto que isso funciona no PHP5.5

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

Permite criar uma coleção de fechamentos de objetos psuedo.

Gordon Rouse
fonte