The PHP manual for anonymous functions (ie, Closures) states that:
Anonymous functions are currently implemented using the Closure class. This is an implementation detail and should not be relied upon.
(Emphasis is my own)
Is it possible to test a variable, such that the test returns true only if the variable is a Closure, without referring to the Closure class?
In other words, how can I rewrite the following such that it will raise an error when $bar
is anything but an anonymous function:
function foo(Closure $bar) { $bar(); }
EDIT: Based on the answers received, here is an example test.
Notes:
ReflectionFunction::isClosure()
method seems to be be almost useless: by the time you've done the checks required to make sure that ReflectionFunction can actually be instantiated (can't take a Class except for a Closure), you've eliminated all other options.Code:
/**
* Return true if and only if the passed argument is a Closure.
*/
function testClosure($a) {
// Must be Callback, Labmda, Functor or Closure:
if(!is_callable($a)) return false;
// Elminate Callbacks & Lambdas
if(!is_object($a)) return false;
// Eliminate Functors
//$r = new ReflectionFunction($a); <-- fails if $a is a Functor
//if($r->isClosure()) return true;
return false;
}
Test case:
//////////// TEST CASE /////////////
class CallBackClass {
function callBackFunc() {
}
}
class Functor {
function __invoke() {
}
}
$functor = new Functor();
$lambda = create_function('', '');
$callback = array('CallBackClass', 'callBackFunc');
$array = array();
$object = new stdClass();
$closure = function() { ; };
echo "Is it a closure? \n";
echo "Closure: " . (testClosure($closure) ? "yes" : "no") . "\n";
echo "Null: " . (testClosure(null) ? "yes" : "no") . "\n";
echo "Array: " . (testClosure($array) ? "yes" : "no") . "\n";
echo "Callback: " . (testClosure($callback) ? "yes" : "no") . "\n";
echo "Labmda: " .(testClosure($lambda) ? "yes" : "no") . "\n";
echo "Invoked Class: " . (testClosure($functor) ? "yes" : "no") . "\n";
echo "StdObj: " . (testClosure($object) ? "yes" : "no") . "\n";
-
You can also use
ReflectionFunctionAbstract::isClosure
— Checks if closure
Example:
$poorMansLambda = create_function('', 'return TRUE;');
$rf = new ReflectionFunction($poorMansLambda);
var_dump( $rf->isClosure() ); // FALSE
$lambda = function() { return TRUE; };
$rf = new ReflectionFunction($lambda);
var_dump( $rf->isClosure() ); // TRUE
$closure = function() use ($lambda) { return $lambda(); };
$rf = new ReflectionFunction($closure);
var_dump( $rf->isClosure() ); // TRUE
Note that the above will only return TRUE
for PHP 5.3 Lambdas and Closures. If you just want to know whether an argument can be used as a callback, is_callable
will perform better.
EDIT If you want to include Functors as well, you can do (as of PHP 5.3.3)
$rf = new ReflectionObject($functorOrClosureOrLambda);
var_dump( $rf->hasMethod('__invoke') ); // TRUE
or
method_exists($functorOrClosureOrLambda, '__invoke');
with the latter being the faster alternative.
A Closure
instance is basically just a class that has an __invoke
function which you fed the method body on the fly. But since this is testing for an implementation detail, I'd say it is as unreliable as testing for the Closure
Class Name.
EDIT Since you mention you cannot reliably test via the Reflection API due to it raising an error when passing a Functor to ReflectionFunctionAbstract::isClosure
, try if the following solution suits your needs:
function isClosure($arg)
{
if(is_callable($arg, FALSE, $name)) {
is_callable(function() {}, TRUE, $implementation);
return ($name === $implementation);
}
}
This will check if the passed argument is callable. The $name
argument stores the callable name. For closures, this is currently Closure::__invoke
. Since this will be the same for any Closures/Lambdas, we can compare the name of the passed argument against an arbitrary other Closure/Lambda. If they are equal, the argument must be a Closure/Lambda. Determining the callable name at runtime has the added benefit that you dont have to hardcode assumptions about the implementation details into your sourcecode. Passing a functor will return FALSE
, because it wont have the same callable name. Since this does not rely on the Reflection API, it is also likely a bit faster.
The above could be more elegantly written as
function isClosure($arg) {
$test = function(){};
return $arg instanceof $test;
}
is_callable
and !is_array
might help you along. Note that you cannot rely on PHP's type hinting/checking this way, since you would have to check the variable inside the function and throw something, e.g. an InvalidArgumentException
yourself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With