__invoke() on callable Array or String

2019-02-25 22:11发布

问题:

How would one write PHP code to call all "Callables" with __invoke()?

The desire here is pass by reference, which is deprecated with call_user_func[_array](). I did see that there is a package out there, TRex\Reflection\CallableReflection, but this seems to utilize call_user_func() in the background, and would suffer the same issue.

<?php

function passthrough_invoke(callable $callback) {
    return $callback->__invoke();
}

function passthrough_user(callable $callback) {
    return call_user_func($callback);
}

function test_func() { return "func_string\n"; };

class test_obj {

    function test_method() {
        return "obj_method\n";
    }
}
print_r("Call User Func Works:\n");
echo passthrough_user(function() { return "func_closure\n"; });
echo passthrough_user(array(new test_obj, 'test_method'));
echo passthrough_user('test_func');

print_r("\n__invoke dies:\n");
echo passthrough_invoke(function() { return "func_closure\n"; });
echo passthrough_invoke(array(new test_obj, 'test_method'));
echo passthrough_invoke('test_func');

This question could also moonlight as "Is there a way that is not going to be deprecated that you can call a callback with pass by reference?", but I find the current question more interesting.

Notes:

The primary goal is to have the callback act as a full function, and have all of the niceties of that, primarily including Pass By Reference, which __invoke($args, ...) allows.

using func_get_args(), or ...$args (variadic function on a wrapper) would not work, as you will still be left with using call_user_func_array($callback, $arg_array), which will not support Pass By Reference.

Notes 2:

I just learned that you can CALL using variadic parameters as well in the next PHP: function_name(...$args). Does this support pass by reference?

Further we still run in to the issue that $callback_array = array($object, 'method'); is a callable, but not by $callback_array();, and certainly not by $callback_array(...$args);. Further, I should clarify that the question is really about writing code that will not break in later releases that can do this.

IE: I can write it now, run it tomorrow.

Which is looking dimmer and dimmer of a prospect.

回答1:

It’s easy to implement for calling class member:

<?php

function passthrough_invoke(callable $callback) {
    return $callback->__invoke();
}

function passthrough_user(callable $callback) {
    return call_user_func($callback);
}

function test_func() { return "func_string\n"; };

class test_obj {
    public function test_method() {
        return "obj_method\n";
    }
}

class CallableWrapper {
    private $inst, $meth;
    public function __construct( $inst, $meth ) {
      $this->inst = $inst;
      $this->meth = $meth;
    }
    public function __invoke() {
        echo $this->inst->{$this->meth}();
    }
}

print_r("Call User Func Works:\n");
echo passthrough_user(function() { return "func_closure\n"; });
echo passthrough_user(array(new test_obj, 'test_method'));
echo passthrough_user('test_func');

print_r("\n__invoke rocks:\n");
echo passthrough_invoke( function() { return "func_closure\n"; } );
echo passthrough_invoke( 
  new CallableWrapper( new test_obj, 'test_method' ) 
);

// ⇒
// __invoke rocks:
// func_closure
// obj_method

For global scoped function is should be doable as well, but I reject to try to implement wrong patterns :)