Aufruf der Schließung, die der Objekteigenschaft direkt zugewiesen ist

108

Ich möchte in der Lage sein, einen Abschluss aufzurufen, den ich der Eigenschaft eines Objekts direkt zuweise, ohne den Abschluss einer Variablen neu zuzuweisen und ihn dann aufzurufen. Ist das möglich?

Der folgende Code funktioniert nicht und verursacht Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
Kendall Hopkins
quelle
1
Dies ist genau das, was Sie brauchen: github.com/ptrofimov/jslikeobject Noch mehr: Sie können $ this in Closures verwenden und Vererbung verwenden. Nur PHP> = 5,4!
Renato Cuccinotto

Antworten:

106

Ab PHP7 können Sie dies tun

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

oder verwenden Sie Closure :: call () , obwohl dies bei a nicht funktioniert StdClass.


Vor PHP7 müssten Sie die magische __callMethode implementieren , um den Aufruf abzufangen und den Rückruf aufzurufen (was StdClassnatürlich nicht möglich ist , da Sie die __callMethode nicht hinzufügen können ).

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

Beachten Sie, dass Sie dies nicht tun können

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

im __callKörper, weil dies __callin einer Endlosschleife auslösen würde .

Gordon
quelle
2
Manchmal finden Sie diese Syntax nützlich - call_user_func_array ($ this -> $ property, $ args); Wenn es um aufrufbare Klasseneigenschaften geht, nicht um eine Methode.
Nikita Gopkalo
104

Sie können dies tun, indem Sie beim Schließen __invoke aufrufen, da dies die magische Methode ist, mit der sich Objekte wie Funktionen verhalten:

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

Das funktioniert natürlich nicht, wenn der Rückruf ein Array oder eine Zeichenfolge ist (was auch in PHP gültige Rückrufe sein können) - nur für Closures und andere Objekte mit __invoke-Verhalten.

Brilliand
quelle
3
@ MarcioAlmada sehr hässlich.
Mahn
1
@ Mahn Ich denke, es ist expliziter als die akzeptierte Antwort. Explizit ist in diesem Fall besser. Wenn Sie sich wirklich für eine "süße" Lösung interessieren, call_user_func($obj->callback)ist das nicht so schlimm.
Marcio
Funktioniert aber auch call_user_funcmit Saiten und das ist nicht immer praktisch
Gherman
2
@cerebriform semantisch macht es keinen Sinn zu tun, $obj->callback->__invoke();wann man es erwarten würde $obj->callback(). Es ist nur eine Frage der Konsistenz.
Mahn
3
@ Mahn: Zugegeben, es ist nicht konsistent. Konsistenz war jedoch nie wirklich eine Stärke von PHP. :) Aber ich denke, es macht Sinn, wenn man die Objektnatur betrachtet : $obj->callback instanceof \Closure.
Bischof
24

Ab PHP 7 können Sie Folgendes tun:

($obj->callback)();
Korikulum
quelle
Solch gesunder Menschenverstand, aber es ist rein schlecht. Die große Kraft von PHP7!
tfont
10

Seit PHP 7 kann ein Abschluss mit folgender call()Methode aufgerufen werden :

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

Da PHP 7 auch Operationen mit beliebigen (...)Ausdrücken ausführen kann (wie von Korikulum erklärt ):

($obj->callback)();

Andere gängige PHP 5- Ansätze sind:

  • mit der magischen Methode __invoke()(wie von Brilliand erklärt )

    $obj->callback->__invoke();
  • mit der call_user_func()Funktion

    call_user_func($obj->callback);
  • Verwenden einer Zwischenvariablen in einem Ausdruck

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

Jeder Weg hat seine eigenen Vor- und Nachteile, aber die radikalste und endgültigste Lösung bleibt die von Gordon vorgestellte .

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
quelle
7

Es scheint möglich zu sein, mit call_user_func().

call_user_func($obj->callback);

aber nicht elegant ... Was @Gordon sagt, ist wahrscheinlich der einzige Weg.

Pekka
quelle
7

Nun, wenn Sie wirklich darauf bestehen. Eine weitere Problemumgehung wäre:

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

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

$obj['callback']();

Aber das ist nicht die schönste Syntax.

Allerdings behandelt der PHP - Parser immer T_OBJECT_OPERATOR, IDENTIFIER, (als Methodenaufruf. Es scheint keine Problemumgehung zu geben, ->um die Methodentabelle zu umgehen und stattdessen auf die Attribute zuzugreifen.

Mario
quelle
7

Ich weiß, dass dies alt ist, aber ich denke, dass Traits dieses Problem gut lösen, wenn Sie PHP 5.4+ verwenden

Erstellen Sie zunächst ein Merkmal, das Eigenschaften aufrufbar macht:

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

Dann können Sie dieses Merkmal in Ihren Klassen verwenden:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Jetzt können Sie Eigenschaften über anonyme Funktionen definieren und direkt aufrufen:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
SteveK
quelle
Wow :) Das ist viel eleganter als das, was ich versucht habe. Meine einzige Frage ist die gleiche wie für britische Hot / Cold-Tap-Anschlusselemente: WARUM ist sie nicht bereits eingebaut?
dkellner
Vielen Dank :). Es ist wahrscheinlich nicht eingebaut, weil es mehrdeutig ist. Stellen Sie sich in meinem Beispiel vor, es gäbe tatsächlich eine Funktion namens "add" und eine Eigenschaft namens "add". Das Vorhandensein der Klammer weist PHP an, nach einer Funktion mit diesem Namen zu suchen.
SteveK
2

Nun, es sollte betont werden, dass das Speichern des Verschlusses in einer Variablen und das Aufrufen der Variablen tatsächlich (seltsam) schneller ist, abhängig von der Anrufmenge. Mit xdebug (so sehr genaues Messen) wird es ziemlich viel 1,5 (der Faktor, bei dem eine Variable verwendet wird, anstatt den __invoke direkt aufzurufen. Speichern Sie den Verschluss stattdessen einfach in einer Variablen und rufen Sie ihn auf.

Kmtdk
quelle
2

Aktualisiert:

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

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

$callback = $obj->callback;  
$callback();
M Rostami
quelle
Hast du das versucht? Es funktioniert nicht. Call to undefined method AnyObject::callback()(Klasse AnyObject existiert natürlich.)
Kontrollfreak
1

Hier ist eine weitere Alternative, die auf der akzeptierten Antwort basiert, aber stdClass direkt erweitert:

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

Anwendungsbeispiel:

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

Sie sind wahrscheinlich besser dran call_user_funcoder __invokeobwohl.

Mahn
quelle
0

Wenn Sie PHP 5.4 oder höher verwenden, können Sie einen Aufruf an den Bereich Ihres Objekts binden, um benutzerdefiniertes Verhalten aufzurufen. Wenn Sie zum Beispiel Folgendes einrichten möchten:

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

Und Sie haben an einer Klasse wie dieser gearbeitet.

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

Sie können Ihre eigene Logik ausführen, als würden Sie im Rahmen Ihres Objekts arbeiten

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

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

Ich stelle fest, dass dies in PHP5.5 funktioniert

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

Ermöglicht das Erstellen einer Pseudoobjektsammlung von Verschlüssen.

Gordon Rouse
quelle