Chiamata chiusura assegnata direttamente alla proprietà dell'oggetto


109

Vorrei poter chiamare una chiusura che assegno direttamente alla proprietà di un oggetto senza riassegnare la chiusura a una variabile e quindi chiamarla. È possibile?

Il codice seguente non funziona e causa Fatal error: Call to undefined method stdClass::callback().

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

1
Questo è esattamente ciò di cui hai bisogno: github.com/ptrofimov/jslikeobject Ancora di più: puoi usare $ this all'interno delle chiusure e usare l'ereditarietà. Solo PHP> = 5.4!
Renato Cuccinotto

Risposte:


106

A partire da PHP7, puoi farlo

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

o usa Closure :: call () , anche se questo non funziona su un file StdClass.


Prima di PHP7, dovresti implementare il __callmetodo magico per intercettare la chiamata e invocare il callback (cosa StdClassovviamente non possibile perché non puoi aggiungere il __callmetodo)

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

Nota che non puoi farlo

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

nel __callcorpo, perché questo si innescherebbe __callin un ciclo infinito.


2
A volte troverai utile questa sintassi: call_user_func_array ($ this -> $ property, $ args); quando si tratta di proprietà di una classe richiamabile, non di un metodo.
Nikita Gopkalo

104

Puoi farlo chiamando __invoke alla chiusura, poiché questo è il metodo magico che gli oggetti usano per comportarsi come le funzioni:

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

Ovviamente non funzionerà se il callback è un array o una stringa (che può anche essere callback validi in PHP) - solo per chiusure e altri oggetti con comportamento __invoke.


3
@marcioAlmada molto brutto però.
Mahn

1
@Mahn Penso che sia più esplicito della risposta accettata. Esplicito è meglio in questo caso. Se ti interessa davvero una soluzione "carina", call_user_func($obj->callback)non è poi così male.
marcio

Ma call_user_funcfunziona anche con gli archi e non è sempre conveniente
Gherman

2
@cerebriform semanticamente non ha senso avere a che fare $obj->callback->__invoke();quando ci si aspetterebbe che fosse $obj->callback(). È solo una questione di coerenza.
Mahn

3
@ Mahn: Concesso, non è coerente. La coerenza non è mai stata davvero un punto forte di PHP, però. :) Ma penso che non ha senso, se si considera la natura oggetto: $obj->callback instanceof \Closure.
vescovo

24

A partire da PHP 7 puoi fare quanto segue:

($obj->callback)();

Un tale buon senso, ma è puramente tosto. La grande potenza di PHP7!
tfont

10

Dal momento che PHP 7 una chiusura può essere chiamata utilizzando ilcall() metodo:

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

Poiché PHP 7 è possibile eseguire operazioni anche su (...)espressioni arbitrarie (come spiegato da Korikulum ):

($obj->callback)();

Altri approcci PHP 5 comuni sono:

  • usando il metodo magico __invoke()(come spiegato da Brilliand )

    $obj->callback->__invoke();
  • utilizzando la call_user_func()funzione

    call_user_func($obj->callback);
  • utilizzando una variabile intermedia in un'espressione

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

Ogni modo ha i suoi pro e contro, ma la soluzione più radicale e definitiva rimane comunque quella presentata da 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();

7

Sembra essere possibile usare call_user_func().

call_user_func($obj->callback);

non è elegante, però .... Quello che dice @Gordon è probabilmente l'unica strada da percorrere.


7

Beh, se davvero insisti. Un'altra soluzione potrebbe essere:

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

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

$obj['callback']();

Ma non è la sintassi migliore.

Tuttavia, il parser PHP tratta sempre T_OBJECT_OPERATOR, IDENTIFIER, (come chiamata di metodo. Non sembra esserci alcuna soluzione alternativa per ->bypassare la tabella dei metodi e accedere invece agli attributi.


7

So che questo è vecchio, ma penso che i tratti gestiscano bene questo problema se stai usando PHP 5.4+

Innanzitutto, crea un tratto che renda le proprietà richiamabili:

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

Quindi, puoi usare quel tratto nelle tue classi:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Ora puoi definire proprietà tramite funzioni anonime e chiamarle direttamente:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4

Wow :) Questo è molto più elegante di quello che stavo cercando di fare. La mia unica domanda è la stessa degli elementi del connettore del rubinetto britannico caldo / freddo: PERCHÉ non è già integrato ??
dkellner

Grazie :). Probabilmente non è integrato a causa dell'ambiguità che crea. Immagina nel mio esempio, se ci fosse effettivamente una funzione chiamata "add" e una proprietà chiamata "add". La presenza delle parentesi indica a PHP di cercare una funzione con quel nome.
SteveK

2

beh, va sottolineato che memorizzare la chiusura in una variabile, e chiamare la variabile è in realtà (stranamente) più veloce, a seconda dell'importo della chiamata, diventa parecchio, con xdebug (misurazione quindi molto precisa), stiamo parlando di 1,5 (il fattore, utilizzando una variabile, invece di chiamare direttamente __invoke. Quindi, invece, basta memorizzare la chiusura in una variabile e chiamarla.


2

aggiornato:

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

PHP> = 7:

($obj->callback)();

PHP> = 5.4:

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

Hai provato questo? Non funziona. Call to undefined method AnyObject::callback()(La classe AnyObject esiste, ovviamente.)
Kontrollfreak

1

Ecco un'altra alternativa basata sulla risposta accettata ma estendendo direttamente stdClass:

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

Esempio di utilizzo:

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

Probabilmente stai meglio usando call_user_funco __invokeanche se.


0

Se utilizzi PHP 5.4 o versioni successive, potresti associare un chiamabile all'ambito del tuo oggetto per richiamare un comportamento personalizzato. Quindi, ad esempio, se dovessi avere la seguente configurazione ..

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

E stavi operando in una classe del genere ..

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

Potresti eseguire la tua logica come se stessi operando dall'ambito del tuo oggetto

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

echo $foo->getValue(); // prints "something else"

0

Noto che funziona in PHP5.5

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

Consente di creare una raccolta di chiusure psuedo-object.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.