The problem: a function that calls another function which is passed in the arguments of the initial function. The callable might re-use the arguments of the initial function. Whatever the callable is we want a simple function for use in our code (here is where closure comes).
Example: a generic Header class that sends files (cached, not cached, for download, inline etc.). Probably readfile() is all you need right?
But what when a captcha library has a function output() that you want to be used in the place of readfile()?
No big deal you might say, pass a callable in the function's arguments, but let's unwind the possibilities of the callable argument:
1. it's another function in this scope,
2. it's a class method,
3. it's a static method of a class.
Php uses different helpers to call this argument-function:
1. call_user_func_array(),
2, 3. call_user_func_array() or Reflection.
A generic solution that exploits most of php's advanced features: closures, callables, Reflection.
/**
* @param string $file The path of a file
* @param ... more specific arguments here ...
* @param string[] $call A hashed array that contains the function we want to
* operate on our arguments and follows some rules as follows
* -key 'class': the class name the function belongs to or null,
* -key 'construct': the class's arguments on construction or null,
* -key 'args': an array of arguments explicit for the callable or null,
* -key 'callable': the name of the function (null not allowed).
*/
function generic($file = null, $call = array('callable' => '\readfile')) {
if (isset($call['class'])) {
// a closure...
$action = function() use ($call) {
$method = new \ReflectionMethod($call['class'], $call['callable']);
if ($method->isStatic()) {
// YOU decide what to do with the arguments in generic()
if (isset($call['args'])) {
$method->invokeArgs(null, $call['args']);
} else {
$method->invoke(null);
}
} else {
$obj = new \ReflectionClass($call['class']);
if (isset($call['construct'])) {
$obj->newInstanceArgs($call['construct']);
} else {
$obj->newInstance();
}
// YOU decide what to do with the arguments in generic()
if (isset($call['args'])) {
$method->invokeArgs($obj, $call['args']);
} else {
$method->invoke($obj);
}
}
};
} else {
// another closure...
$action = function() use ($call, $file) {
// YOU decide what to do with the arguments in generic()
if (isset($call['args'])) {
\call_user_func_array($call['callable'], $args);
} else {
\call_user_func_array($call['callable'], array($file));
}
};
}
// ...some commands here...
\header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
\header("Cache-Control: no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
\header("Content-Description: File Transfer");
\header("Content-Disposition: inline; filename=\"" . $file . "\"");
\header("Content-Type: image/jpeg");
// adjust the following...
\header('Content-Length: ' . \filesize($file));
// see how we use a closure
$action();
}
// let's call the generic()
generic('client/assets/404.jpg');
Just an example use of closures...