Wywołanie domknięcia przypisanego bezpośrednio do właściwości obiektu

109

Chciałbym móc wywołać zamknięcie, które przypisuję bezpośrednio do właściwości obiektu, bez ponownego przypisywania zamknięcia do zmiennej, a następnie wywoływania jej. czy to możliwe?

Poniższy kod nie działa i powoduje Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
Kendall Hopkins
źródło
1
Właśnie tego potrzebujesz: github.com/ptrofimov/jslikeobject Co więcej: możesz użyć $ this wewnątrz domknięć i użyć dziedziczenia. Tylko PHP> = 5,4!
Renato Cuccinotto,

Odpowiedzi:

106

Od PHP7 możesz to zrobić

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

lub użyj Closure :: call () , chociaż to nie działa naStdClass .


Przed PHP7 musiałbyś zaimplementować magiczną __callmetodę, aby przechwycić wywołanie i wywołać wywołanie zwrotne (co StdClassoczywiście nie jest możliwe , ponieważ nie możesz dodać __callmetody)

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

Pamiętaj, że nie możesz tego zrobić

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

w __callciele, ponieważ wywołałoby __callto nieskończoną pętlę.

Gordon
źródło
2
Czasami ta składnia okaże się przydatna - call_user_func_array ($ this -> $ property, $ args); kiedy chodzi o wywoływalną właściwość klasy, a nie metodę.
Nikita Gopkalo
105

Możesz to zrobić, wywołując __invoke przy zamykaniu, ponieważ jest to magiczna metoda, której używają obiekty, aby zachowywać się jak funkcje:

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

Oczywiście to nie zadziała, jeśli wywołanie zwrotne jest tablicą lub ciągiem znaków (które mogą być również prawidłowymi wywołaniami zwrotnymi w PHP) - tylko dla zamknięć i innych obiektów z zachowaniem __invoke.

Brilliand
źródło
3
@marcioAlmada jest bardzo brzydka.
Mahn,
1
@Mahn Myślę, że jest to bardziej wyraźne niż zaakceptowana odpowiedź. W tym przypadku wyraźne jest lepsze. Jeśli naprawdę zależy ci na „uroczym” rozwiązaniu, call_user_func($obj->callback)nie jest tak źle.
marcio,
Ale call_user_funcdziała też ze strunami i nie zawsze jest to wygodne
Gherman
2
@cerebriform semantycznie nie ma sensu robić tego, $obj->callback->__invoke();gdy można by się tego spodziewać $obj->callback(). To tylko kwestia spójności.
Mahn
3
@Mahn: To prawda, nie jest spójne. Jednak spójność nigdy nie była mocną stroną PHP. :) Ale myślę, że to ma sens, gdy weźmie się pod uwagę charakter obiektu: $obj->callback instanceof \Closure.
biskup
24

Od PHP 7 możesz wykonać następujące czynności:

($obj->callback)();
Korikulum
źródło
Taki zdrowy rozsądek, ale to czysto zły. Wielka moc PHP7!
tfont
10

Od PHP 7 domknięcie można wywołać call()metodą:

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

Ponieważ PHP 7 jest możliwe do wykonywania operacji również na dowolnych (...)wyrażeniach (jak wyjaśniono w Korikulum ):

($obj->callback)();

Inne popularne podejścia do PHP 5 to:

  • używając magicznej metody __invoke()(jak wyjaśnił Brilliand )

    $obj->callback->__invoke();
  • za pomocą call_user_func()funkcji

    call_user_func($obj->callback);
  • użycie zmiennej pośredniej w wyrażeniu

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

Każdy sposób ma swoje wady i zalety, ale najbardziej radykalnym i ostatecznym rozwiązaniem pozostaje to, które przedstawił 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
źródło
7

Wydaje się, że jest to możliwe przy użyciu call_user_func().

call_user_func($obj->callback);

choć nie eleganckie ... To, co mówi @Gordon, jest prawdopodobnie jedyną drogą.

Pekka
źródło
7

Cóż, jeśli naprawdę nalegasz. Innym obejściem byłoby:

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

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

$obj['callback']();

Ale to nie jest najładniejsza składnia.

Jednakże parsera PHP zawsze traktuje T_OBJECT_OPERATOR, IDENTIFIER, (jako metoda połączenia. Wydaje się, że nie ma obejścia umożliwiającego ->ominięcie tabeli metod i uzyskanie dostępu do atrybutów.

mario
źródło
7

Wiem, że to jest stare, ale myślę, że funkcje ładnie radzą sobie z tym problemem, jeśli używasz PHP 5.4+

Najpierw utwórz cechę, która umożliwia wywołanie właściwości:

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

Następnie możesz użyć tej cechy na swoich zajęciach:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Teraz możesz definiować właściwości za pomocą funkcji anonimowych i wywoływać je bezpośrednio:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
SteveK
źródło
Wow :) To jest o wiele bardziej eleganckie niż to, co próbowałem zrobić. Moje jedyne pytanie jest takie samo, jak w przypadku brytyjskich elementów złączek do kranu na gorąco / zimno: DLACZEGO jeszcze nie jest wbudowane?
dkellner
Dzięki :). Prawdopodobnie nie jest wbudowany ze względu na dwuznaczność, którą tworzy. Wyobraź sobie w moim przykładzie, gdyby faktycznie istniała funkcja o nazwie „add” i właściwość o nazwie „add”. Obecność nawiasów mówi PHP, aby szukał funkcji o tej nazwie.
SteveK
2

cóż, należy podkreślić, że przechowywanie zamknięcia w zmiennej i wywołanie zmiennej jest faktycznie (dziwnie) szybsze, w zależności od kwoty połączenia, staje się całkiem sporo, z xdebug (tak bardzo precyzyjnym pomiarem), o którym mówimy 1,5 (współczynnik, używając zmiennej, zamiast bezpośrednio wywoływać __invoke, więc zamiast tego po prostu zapisz zamknięcie w zmiennej i wywołaj ją.

Kmtdk
źródło
1

Oto kolejna alternatywa oparta na zaakceptowanej odpowiedzi, ale bezpośrednio rozszerzająca stdClass:

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

Przykład użycia:

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

Prawdopodobnie lepiej jest użyć call_user_funclub __invokechociaż.

Mahn
źródło
1

Zaktualizowano:

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

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

$callback = $obj->callback;  
$callback();
M Rostami
źródło
Próbowałeś tego? To nie działa. Call to undefined method AnyObject::callback()(Oczywiście klasa AnyObject istnieje.)
Kontrollfreak
0

Jeśli używasz PHP 5.4 lub nowszego, możesz powiązać wywołanie z zakresem swojego obiektu, aby wywołać niestandardowe zachowanie. Na przykład, jeśli miałbyś skonfigurować następującą konfigurację ...

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

A ty działałeś na takiej klasie ...

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

Możesz uruchomić własną logikę, tak jakbyś działał z zakresu swojego obiektu

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

echo $foo->getValue(); // prints "something else"
Lee Davis
źródło
0

Zauważam, że działa to w PHP5.5

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

Umożliwia tworzenie kolekcji domknięć typu pseudo-obiektowego.

Gordon Rouse
źródło