Руководство PHP для анонимных функций (т. Е. Closures) гласит, что:
Анонимные функции в настоящее время реализованы с использованием класса Closure. Это деталь реализации, на которую нельзя положиться .
(Акцент – мой собственный)
Возможно ли протестировать переменную, так что тест возвращает true, только если переменная является закрытием, не ссылаясь на класс Closure ?
Другими словами, как я могу переписать следующее, чтобы он вызывал ошибку, когда $bar
– это нечто вроде анонимной функции:
функция foo (Closure $ bar) { $ Бар (); }
EDIT: на основании полученных ответов, вот пример теста.
Заметки:
ReflectionFunction::isClosure()
кажется почти бесполезным: к тому времени, когда вы сделали проверки, необходимые, чтобы убедиться, что ReflectionFunction действительно может быть создан (не может принимать класс, за исключением закрытия) , вы устранили все другие варианты. Код:
/** * 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 ///////////// 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";
–
Вы также можете использовать
ReflectionFunctionAbstract::isClosure
– проверяет, закрыто
Пример:
$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
Обратите внимание, что вышеизложенное вернет TRUE
для PHP 5.3 Lambdas и Closures. Если вы просто хотите знать, может ли аргумент использоваться в качестве обратного вызова, is_callable
будет работать лучше.
EDIT. Если вы хотите также включить функторы, вы можете сделать ( с PHP 5.3.3 )
$rf = new ReflectionObject($functorOrClosureOrLambda); var_dump( $rf->hasMethod('__invoke') ); // TRUE
или
method_exists($functorOrClosureOrLambda, '__invoke');
причем последняя является более быстрой альтернативой.
Экземпляр Closure
– это просто класс, в котором есть функция __invoke
которую вы кормили телом метода на лету. Но так как это тестирование для детали реализации, я бы сказал, что это так же ненадежно, как тестирование для имени класса Closure
.
EDIT. Поскольку вы упоминаете, что вы не можете надежно протестировать через Reflection API из-за того, что он поднимает ошибку при передаче Functor в ReflectionFunctionAbstract::isClosure
, попробуйте, если следующее решение соответствует вашим потребностям:
function isClosure($arg) { if(is_callable($arg, FALSE, $name)) { is_callable(function() {}, TRUE, $implementation); return ($name === $implementation); } }
Это проверит, разрешен ли переданный аргумент. Аргумент $name
хранит вызываемое имя. Для закрытий это в настоящее время Closure::__invoke
. Поскольку это будет одинаково для любых Closures / Lambdas, мы можем сравнить имя переданного аргумента с любым другим Closure / Lambda. Если они равны, аргумент должен быть Закрытием / Лямбдой. Определение имени вызываемого пользователя во время выполнения имеет дополнительное преимущество, которое вам не нужно жестко задавать предположения о деталях реализации в исходном коде. Передача функтора вернет FALSE
, потому что у него не будет такого же вызываемого имени. Поскольку это не зависит от API Reflection, это также, вероятно, немного быстрее.
Вышеизложенное может быть более элегантно написано как
function isClosure($arg) { $test = function(){}; return $arg instanceof $test; }
is_callable
и !is_array
может помочь вам. Обратите внимание, что вы не можете полагаться на тип и тип проверки PHP, так как вам придется проверять переменную внутри функции и бросать что-то, например InvalidArgumentException
.