<?php

class Invoker
{
    public static function invoke(callable $callable, &$p1 = null, &$p2 = null)
    {
        if (is_string($callable) && strpos($callable, '::')) {
            // Strings are usually free function names, but they can also
            // specify a static method with ClassName::methodName --
            // if that's the case, convert to array form
            $callable = explode('::', $callable);
        }
 
        // Get a ReflectionFunctionAbstract instance that will give us
        // information about the invocation target's parameters
        if (is_string($callable)) {
            // Now we know it refers to a free function
            $reflector = new ReflectionFunction($callable);
        }
        else if (is_array($callable)) {
            list ($class, $method) = $callable;
            $reflector = new ReflectionMethod($class, $method);
        }
        else {
            // must be an object -- either a closure or a functor
            $reflector = new ReflectionObject($callable);
            $reflector = $reflector->getMethod('__invoke');
        }

        $forwardedArguments = [];
        $incomingArgumentCount = func_num_args() - 1;
        $paramIndex = 0;
 
        foreach($reflector->getParameters() as $param) {
            if ($paramIndex >= $incomingArgumentCount) {
                if (!$param->isOptional()) {
                    // invocation target requires parameter that was not passed,
                    // perhaps we want to handle the error right now?
                }
                
                break; // call target will less parameters than it can accept
            }
            
            $forwardedArguments[] = &${'p'.(++$paramIndex)};
        }
        
        return call_user_func_array($callable, $forwardedArguments);
    }
}

// free function
function test(&$x, $y) { $x = 'foo'; $y = 'bar'; }

// method
class dummy {
    public static function test(&$x, $y) { $x = 'foo'; $y = 'bar'; }
}

// functor
class test {
    public function __invoke(&$x, $y) { $x = 'foo'; $y = 'bar'; }
}

// closure
$closure = function(&$x, $y) { $x = 'foo'; $y = 'bar'; };

$x = 'x'; $y = 'y';
Invoker::invoke('test', $x, $y);
echo "After invoking: \$x = $x, \$y = $y\n";

$x = 'x'; $y = 'y';
Invoker::invoke(['dummy', 'test'], $x, $y);
echo "After invoking: \$x = $x, \$y = $y\n";

$x = 'x'; $y = 'y';
Invoker::invoke(new test(), $x, $y);
echo "After invoking: \$x = $x, \$y = $y\n";

$x = 'x'; $y = 'y';
Invoker::invoke($closure, $x, $y);
echo "After invoking: \$x = $x, \$y = $y\n";
