Created
May 23, 2012 03:50
-
-
Save rintaun/2773168 to your computer and use it in GitHub Desktop.
closurize(): Converts any valid PHP callable into a Closure. Requires PHP 5.4.0+.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Converts any valid PHP callable into a Closure. Requires PHP 5.4.0+. | |
* | |
* The ramifications of this are many, but basically it means that any function | |
* or method can be converted into a Closure, bound to another scope, and | |
* executed easily. Works properly even with private methods. | |
* | |
* - On success, returns a Closure corresponding to the provided callable. | |
* - If the parameter is not callable, issues an E_USER_WARNING and returns a | |
* Closure which only returns null. | |
* - In the event of a strange or unrecoverable situation (e.g. providing a | |
* non-static method without an object), an UnexpectedValueException is | |
* thrown. | |
* | |
* @author Matthew Lanigan <[email protected]> | |
* @copyright (c) 2012, Matthew Lanigan | |
* @license http://www.opensource.org/licenses/mit-license.php MIT License | |
* @link https://gist.github.com/2773168 Official closurize() gist | |
* @param callable $callable | |
* @return \Closure | |
* @throws \UnexpectedValueException | |
*/ | |
function closurize($callable) { | |
if ($callable instanceof \Closure) { | |
return $callable; | |
} | |
$is_callable = function($callable) { | |
return \is_callable($callable); | |
}; | |
$error = function() { | |
$debug = \debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); | |
$fmt = 'Parameter 1 for closurize() must be callable ' . | |
'in %s on line %d (issued at %s on line %d)'; | |
$error = \sprintf($fmt, $debug[1]['file'], $debug[1]['line'], | |
$debug[0]['file'], $debug[0]['line']); | |
\trigger_error($error, \E_USER_WARNING); | |
return function() { | |
return null; | |
}; | |
}; | |
$object = null; | |
$class = null; | |
$debug = \debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); | |
if (isset($debug[1]['object']) && \is_object($debug[1]['object'])) { | |
$object = $debug[1]['object']; | |
$class = $debug[1]['class']; | |
$is_callable = $is_callable->bindTo($object, $object); | |
} | |
if (!$is_callable($callable)) { | |
if (isset($callable[0]) && is_object($callable[0])) { | |
$is_callable = $is_callable->bindTo($callable[0], $callable[0]); | |
} else if (isset($callable[0]) && \class_exists($callable[0])) { | |
$is_callable = $is_callable->bindTo(null, $callable[0]); | |
} | |
if (!$is_callable($callable)) { | |
return $error(); | |
} | |
} | |
if (\is_string($callable) && (\strpos($callable, '::') === false)) { | |
$ref = new \ReflectionFunction($callable); | |
return $ref->getClosure(); | |
} else if (\is_string($callable)) { | |
$callable = \explode('::', $callable); | |
} | |
if (!\is_array($callable)) { | |
throw new \UnexpectedValueException('Callable is not string, array, '. | |
'or Closure'); | |
} | |
if (\is_object($callable[0])) { | |
$ref = new \ReflectionMethod($callable[0], $callable[1]); | |
return $ref->getClosure($callable[0]); | |
} | |
if (!\is_string($callable[0])) { | |
throw new \UnexpectedValueException('Callable class is not string ' . | |
'or object'); | |
} | |
switch ($callable[0]) { | |
case 'self': | |
if (!\is_object($object) && \is_null($class)) { | |
return $error(); | |
} | |
$self = function() { | |
return \get_class(); | |
}; | |
$self = $self->bindTo($object, $class); | |
$ref = new \ReflectionMethod($self(), $callable[1]); | |
$callable[0] = $object; | |
break; | |
case 'static': | |
if (!\is_object($object)) { | |
return $error(); | |
} | |
$static = function() { | |
return \get_called_class(); | |
}; | |
$static = $static->bindTo($object, $class); | |
$ref = new \ReflectionMethod($static(), $callable[1]); | |
$callable[0] = $object; | |
break; | |
case 'parent': | |
if (!\is_object($object)) { | |
return $error(); | |
} | |
$parent = function() { | |
return \get_parent_class(); | |
}; | |
$parent = $parent->bindTo($object, $class); | |
$ref = new \ReflectionMethod($parent(), $callable[1]); | |
$callable[0] = $object; | |
break; | |
default: | |
$ref = new \ReflectionMethod($callable[0], $callable[1]); | |
break; | |
} | |
if (!$ref->isStatic() && \is_object($callable[0])) { | |
return $ref->getClosure($callable[0]); | |
} else if (!$ref->isStatic()) { | |
throw new \UnexpectedValueException('Callable method is not static, ' . | |
'but no calling object available'); | |
} | |
return $ref->getClosure(); | |
} |
This seems so pointless! Thanks for the LOL :)
This is not pointless, this will be implemented as Closure::fromCallable() in PHP 7.1 ;)
all those checks and errors are unnecessary.
function closurize ( callable $callable ) {
if ($callable instanceof \Closure) {
return $callable;
}
return function() use ($callable) {
return call_user_func_array($callable,func_get_args());
};
}
@yoosefi no they are not, if you need to bind a new $this
to the closure for example, your method won't work as you wrap the function.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you give some examples of the input that is allowed in the function?