Skip to content

Instantly share code, notes, and snippets.

@rintaun
Created May 23, 2012 03:50
Show Gist options
  • Save rintaun/2773168 to your computer and use it in GitHub Desktop.
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+.
<?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();
}
@CMCDragonkai
Copy link

Can you give some examples of the input that is allowed in the function?

@tox2ik
Copy link

tox2ik commented Feb 5, 2016

This seems so pointless! Thanks for the LOL :)

@abouvier
Copy link

This is not pointless, this will be implemented as Closure::fromCallable() in PHP 7.1 ;)

@yoosefi
Copy link

yoosefi commented Sep 17, 2016

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());
    };
}

@krtek4
Copy link

krtek4 commented Oct 25, 2016

@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