可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I would like to be able to call a closure that I assign to an object\'s property directly without reassigning the closure to a variable and then calling it. Is this possible?
The code below doesn\'t work and causes Fatal error: Call to undefined method stdClass::callback()
.
$obj = new stdClass();
$obj->callback = function() {
print \"HelloWorld!\";
};
$obj->callback();
回答1:
As of PHP7, you can do
$obj = new StdClass;
$obj->fn = function($arg) { return \"Hello $arg\"; };
echo ($obj->fn)(\'World\');
or use Closure::call(), though that doesn\'t work on a StdClass
.
Before PHP7, you\'d have to implement the magic __call
method to intercept the call and invoke the callback (which is not possible for StdClass
of course, because you cannot add the __call
method)
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\');
Note that you cannot do
return call_user_func_array(array($this, $method), $args);
in the __call
body, because this would trigger __call
in an infinite loop.
回答2:
You can do this by calling __invoke on the closure, since that\'s the magic method that objects use to behave like functions:
$obj = new stdClass();
$obj->callback = function() {
print \"HelloWorld!\";
};
$obj->callback->__invoke();
Of course that won\'t work if the callback is an array or a string (which can also be valid callbacks in PHP) - just for closures and other objects with __invoke behavior.
回答3:
As of PHP 7 you can do the following:
($obj->callback)();
回答4:
It seems to be possible using call_user_func()
.
call_user_func($obj->callback);
not elegant, though.... What @Gordon says is probably the only way to go.
回答5:
Well, if you really insist. Another workaround would be:
$obj = new ArrayObject(array(),2);
$obj->callback = function() {
print \"HelloWorld!\";
};
$obj[\'callback\']();
But that\'s not the nicest syntax.
However, the PHP parser always treats T_OBJECT_OPERATOR
, IDENTIFIER
, (
as method call. There seems to be no workaround for making ->
bypass the method table and access the attributes instead.
回答6:
I know this is old, but I think Traits nicely handle this problem if you are using PHP 5.4+
First, create a trait that makes properties callable:
trait CallableProperty {
public function __call($method, $args) {
if (property_exists($this, $method) && is_callable($this->$method)) {
return call_user_func_array($this->$method, $args);
}
}
}
Then, you can use that trait in your classes:
class CallableStdClass extends stdClass {
use CallableProperty;
}
Now, you can define properties via anonymous functions and call them directly:
$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
回答7:
Since PHP 7 a closure can be called using the call()
method:
$obj->callback->call($obj);
Since PHP 7 is possible to execute operations on arbitrary (...)
expressions too (as explained by Korikulum):
($obj->callback)();
Other common PHP 5 approaches are:
using the magic method __invoke()
(as explained by Brilliand)
$obj->callback->__invoke();
using the call_user_func()
function
call_user_func($obj->callback);
using an intermediate variable in an expression
($_ = $obj->callback) && $_();
Each way has its own pros and cons, but the most radical and definitive solution still remains the one presented by 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();
回答8:
Here is another way to successfully call object properties as closure.
When you don\'t want to change core object use this :
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
return function () {
print \"HelloWorld!\";
};
};
$obj->callback();
UPDATE:
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
print \"HelloWorld!\";
};
$callback = $obj->callback;
$callback();
回答9:
well, it should be emphisized that storing the closure in a variable, and call the varible is actually (wierdly) faster, depending on the call amount, it becomes quite a lot, with xdebug (so very precise measuring), we are talking about 1,5 (the factor, by using a varible, instead of directly calling the __invoke. so instead , just store the closure in a varible and call it.
回答10:
Here\'s another alternative based on the accepted answer but extending stdClass directly:
class stdClassExt extends stdClass {
public function __call($method, $args)
{
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
Usage example:
$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();
You are probably better off using call_user_func
or __invoke
though.
回答11:
If you\'re using PHP 5.4 or above you could bind a callable to the scope of your object to invoke custom behavior. So for example if you were to have the following set up..
function run_method($object, Closure $method)
{
$prop = uniqid();
$object->$prop = \\Closure::bind($method, $object, $object);
$object->$prop->__invoke();
unset($object->$prop);
}
And you were operating on a class like so..
class Foo
{
private $value;
public function getValue()
{
return $this->value;
}
}
You could run your own logic as if you were operating from within the scope of your object
$foo = new Foo();
run_method($foo, function(){
$this->value = \'something else\';
});
echo $foo->getValue(); // prints \"something else\"