2

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.

Mike
  • 1,968
  • 18
  • 35
  • "pass by reference, which is deprecated with call_user_func[_array]()" This is not entirely true. *Call-time* pass-by-reference was deprecated in 5.3 and removed in 5.4, for all functions. However, you can still use `call_user_func_array` to pass arguments by reference to a function that accepted those arguments by reference, by putting references into the array, because that is not call-time pass-by-reference. Instead, your problem seems to be that you cannot *get* arguments passed by reference (and keep it as a reference) in a variadic function. – newacct Nov 06 '14 at 20:54
  • I knew this at one point, but I had thought I had read that this, too, was being deprecated. I am looking in to this now... :-/ – Mike Nov 07 '14 at 14:40

1 Answers1

0

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 :)

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • What would you consider "bad practice" about what you believe is doable? – Mike Nov 06 '14 at 17:02
  • This leaves no way to pass variables to the callable. :-/ Sad, I got excited... – Mike Nov 06 '14 at 17:09
  • One might pass the third parameter to call to constructor of `CallableWrapper` and use it in call to `__invoke`. – Aleksei Matiushkin Nov 06 '14 at 17:39
  • That would work for a single parameter, but when the solution needs to cover yet unknown callbacks with unknown argument lists, this would not work. Further, to be syntactically correct, the arguments would be part of `__invoke($arg, ...)`. If you were to need to use this with more than one param, you would be left yet again with `call_user_func_array()`, which will break some day. – Mike Nov 06 '14 at 17:46
  • I am not, how would you then pass those arguments to the callback? `$callback(func_get_args())` would call the callback with one parameter that is an array of the callers parameters. And `call_user_func_array($callback, func_get_args())` would work, but without reference. Substitute `func_get_args()` with `$arg` for a variadic function, and you still have the same situation. Ie. "not kidding." – Mike Nov 06 '14 at 17:55
  • Hmmm... It occurs to me I should add those to the question. – Mike Nov 06 '14 at 17:57
  • If we are talking about `call_user_func` deprecation, then the [variadic parameters](http://docs.php.net/manual/en/migration56.new-features.php#migration56.new-features.variadics) come to the scene. – Aleksei Matiushkin Nov 06 '14 at 18:29