Вызов закрытия, присвоенного свойству объекта напрямую

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 () , хотя это не работает с 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 -> $ property, $ args); когда речь идет о вызываемом свойстве класса, а не о методе.
Никита Гопкало
105

Вы можете сделать это, вызвав __invoke при закрытии, поскольку это магический метод, который объекты используют для поведения функций:

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

Конечно, это не сработает, если обратный вызов представляет собой массив или строку (которые также могут быть действительными обратными вызовами в PHP) - только для замыканий и других объектов с поведением __invoke.

Brilliand
источник
3
@marcioAlmada хотя и очень уродливый.
Mahn
1
@Mahn Я думаю, что это более явный ответ, чем принятый ответ. В этом случае лучше явно. Если вам действительно нужно "симпатичное" решение, call_user_func($obj->callback)не так уж и плохо.
Marcio 06
Но 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 06
10

Начиная с PHP 7, замыкание можно вызвать с помощью call()метода:

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

Поскольку PHP 7 также может выполнять операции с произвольными (...)выражениями (как объяснил Корикулум ):

($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

Я знаю, что это устарело, но я думаю, что Traits прекрасно справляется с этой проблемой, если вы используете 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
Спасибо :). Вероятно, он не встроен из-за создаваемой неоднозначности. Представьте себе в моем примере, если бы на самом деле существовала функция с именем «добавить» и свойство с именем «добавить». Наличие круглых скобок указывает PHP на поиск функции с таким именем.
SteveK 03
2

ну, следует подчеркнуть, что сохранение замыкания в переменной и вызов переменной на самом деле (странно) быстрее, в зависимости от количества вызовов, это становится довольно много, с xdebug (очень точное измерение), мы говорим о 1,5 (коэффициент, используя переменную вместо прямого вызова __invoke. Поэтому вместо этого просто сохраните закрытие в переменной и вызовите его.

Kmtdk
источник
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хотя.

Mahn
источник
1

Обновлено:

$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
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']();

Позволяет создать коллекцию замыканий псевдо-объекта.

Гордон Роуз
источник