Тестирование на закрытие PHP без ссылки на внутренний класс Closure

Руководство PHP для анонимных функций (т. Е. Closures) гласит, что:

Анонимные функции в настоящее время реализованы с использованием класса Closure. Это деталь реализации, на которую нельзя положиться .

(Акцент – мой собственный)

Возможно ли протестировать переменную, так что тест возвращает true, только если переменная является закрытием, не ссылаясь на класс Closure ?

Другими словами, как я могу переписать следующее, чтобы он вызывал ошибку, когда $bar – это нечто вроде анонимной функции:

  функция foo (Closure $ bar) {
     $ Бар ();
 } 

EDIT: на основании полученных ответов, вот пример теста.

Заметки:

  1. Кажется, нет никакого способа разграничения между Функциями и Закрытиями, и что тест, вероятно, является «специфичным для реализации», поскольку используется класс Closure.
  2. Метод (казалось бы, очевидный) ReflectionFunction::isClosure() кажется почти бесполезным: к тому времени, когда вы сделали проверки, необходимые, чтобы убедиться, что ReflectionFunction действительно может быть создан (не может принимать класс, за исключением закрытия) , вы устранили все другие варианты.
  3. В 5.3.0 вы ReflectionClass ($ замыкание) -> hasMethod ('__ invoke') вернули false, поэтому это может быть использовано как тест против Функторов, однако (я сказал) это изменилось с тех пор. Это также подчеркивает хрупкость решения.
  4. Последующие действия от Gordon – Начиная с PHP 5.4 вы можете полагаться на Closure is Closure: php.net/manual/en/class.closure.php

Код:

 /** * 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 .