Виклик закриття, призначеного безпосередньо для власності об'єкта


109

Я хотів би мати можливість викликати закриття, яке я присвоюю властивості об'єкта безпосередньо, не призначаючи закриття змінній, а потім викликаючи її. Чи можливо це?

Приведений нижче код не працює і викликає Fatal error: Call to undefined method stdClass::callback().

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

1
Це саме те, що вам потрібно: github.com/ptrofimov/jslikeobject Навіть більше: ви можете використовувати $ this всередині закриття та використовувати спадщину. Тільки PHP> = 5,4!
Ренато Кукінотто

Відповіді:


106

Щодо PHP7, ви можете це зробити

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

або використовувати Closure :: call () , хоча це не працює на a StdClass.


Перед PHP7 вам доведеться застосувати магічний __callметод для перехоплення виклику та виклику зворотного виклику (що, StdClassзвичайно, неможливо , оскільки ви не можете додати __callметод)

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

Зауважте, що ви не можете зробити

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

в __callтілі, бо це призведе __callдо нескінченної петлі.


2
Іноді ви знайдете цей синтаксис корисним - call_user_func_array ($ this -> $ властивість, $ args); коли мова йде про властивість класу, що викликається, а не метод.
Микита Гопкало

105

Це можна зробити, зателефонувавши __invoke під час закриття, оскільки це магічний метод, який об'єкти використовують для поведінки як функції:

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

Звичайно, це не спрацює, якщо зворотний виклик є масивом або рядком (що також може бути дійсним зворотним зворотом у PHP) - лише для закриття та інших об'єктів з поведінкою __invoke.


3
@marcioAlmada дуже некрасиво.
Ман

1
@Mahn Я думаю, що це явніше, ніж прийнята відповідь. Явне краще в цьому випадку. Якщо ви дійсно піклуєтесь про «миле» рішення, call_user_func($obj->callback)це не так вже й погано.
marcio

Але також call_user_funcпрацює з рядками, і це не завжди зручно
Gherman

2
@cerebriform семантично не має сенсу робити, $obj->callback->__invoke();коли можна було б очікувати $obj->callback(). Це лише питання послідовності.
Ман

3
@Mahn: Зрозуміло, це не відповідає. Однак послідовність ніколи не була сильним для PHP. :) Але я думаю , що це дійсно має сенс, якщо врахувати , об'єкт природи: $obj->callback instanceof \Closure.
єпископ

24

З PHP 7 ви можете зробити наступне:

($obj->callback)();

Такий здоровий глузд, але це суто шкідництво. Велика сила PHP7!
tfont

10

Оскільки закриття PHP 7 може бути викликано call()методом:

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

Оскільки PHP 7 також може виконувати операції з довільними (...)виразами (як пояснив Korikulum ):

($obj->callback)();

Інші поширені підходи PHP 5 :

  • використовуючи магічний метод __invoke()(як пояснив Брілліанд )

    $obj->callback->__invoke();
  • за допомогою call_user_func()функції

    call_user_func($obj->callback);
  • використовуючи проміжну змінну в виразі

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

Кожен спосіб має свої плюси і мінуси, але найбільш радикальним і остаточним рішенням все ж залишається той, який представив Гордон .

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

Здається, це можливо за допомогою call_user_func().

call_user_func($obj->callback);

Не елегантний, хоча .... Те, що говорить @Gordon, це, мабуть, єдиний шлях.


7

Добре, якщо ви дійсно наполягаєте. Іншим рішенням буде:

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

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

$obj['callback']();

Але це не найприємніший синтаксис.

Однак, PHP парсер завжди лікує T_OBJECT_OPERATOR, IDENTIFIER, в (якості виклику методу. Здається, немає жодного вирішення для створення ->обхідної таблиці методів та доступу до атрибутів.


7

Я знаю, що це по-старому, але я думаю, що риси добре вирішують цю проблему, якщо ви використовуєте PHP 5.4+

По-перше, створіть ознаку, яка робить властивості викликати:

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

Потім ви можете використовувати цю ознаку у своїх класах:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Тепер ви можете визначити властивості за допомогою анонімних функцій та викликати їх безпосередньо:

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

Вау :) Це набагато елегантніше, ніж те, що я намагався зробити. Єдине моє запитання таке ж, як і для британських елементів роз'єму гарячого / холодного крана: ЧОМУ він не вбудований ??
dkellner

Дякую :). Він, мабуть, не вбудований через неоднозначність, яку він створює. Уявіть у моєму прикладі, якби насправді існувала функція під назвою "add" та властивість під назвою "add". Наявність круглих дужок спонукає PHP шукати функцію з цим іменем.
SteveK

2

добре, слід зазначити, що зберігання закриття в змінній, а виклик змінної насправді (wierdly) швидше, залежно від суми виклику стає досить багато, з xdebug (настільки дуже точним вимірюванням), ми говоримо про 1,5 (фактор, використовуючи змінну, замість того, щоб безпосередньо викликати __invoke.


2

Оновлено:

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

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

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

Ви пробували це? Це не працює. Call to undefined method AnyObject::callback()(Звичайно, існує клас AnyObject.)
Kontrollfreak

1

Ось ще одна альтернатива, заснована на прийнятій відповіді, але безпосередньо поширює stdClass:

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

Приклад використання:

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

Вам, мабуть, краще використовувати call_user_funcчи __invokeхоч.


0

Якщо ви використовуєте PHP 5.4 або вище, ви можете прив’язати дзвінок до області об’єкта, щоб викликати користувацьку поведінку. Так, наприклад, якщо ви мали б встановити наступне.

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

А ви працювали на такому занятті ..

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

Ви можете запускати власну логіку так, ніби ви працювали в межах об'єкта

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

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

0

Зауважу, що це працює в PHP5.5

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

Дозволяє створити колекцію закритих елементів psuedo-об'єктів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.