Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining if a closure is static in PHP

A closure defined in PHP can also carry the static modifier.

$f = function () { };

$g = static function () { };

The static closure cannot be bound via Closure::bind or Closure::bindTo, and will issue a warning.

$g = Closure::bind(static function () { }, new stdClass());

// Warning: Cannot bind an instance to a static closure in ...

This is also the case of closures created by reflecting a static method with ReflectionMethod::getClosure.

class MyClass
{
    public static function myStaticMethod() { }
}

// reflect MyClass::myStaticMethod,  create an unbound closure, and try to bind it
$f = (new ReflectionMethod(MyClass::class, 'myStaticMethod'))
    ->getClosure()
    ->bindTo(new stdClass());

// Warning: Cannot bind an instance to a static closure in ...

While annoying, this is acceptable; however, how is one to test between a static and non-static closure?

ReflectionMethod::isStatic seemed like it might work, but sensibly doesn't as Closure::__invoke is instance-level, not static.

$f = static function () { };

// reflect Closure::__invoke because I think I'm tricky
$r = new ReflectionMethod($f, '__invoke');

// and it's not static anyway
var_dump($r->isStatic()); // bool(false)

Further, checking ReflectionMethod::getClosureThis can generally work, as a static method must be unbound, however that doesn't cover closures defined outside an instance method, or the corner-case of instance methods that have been unbound.

class MyClass
{
    public function myInstanceMethod() { }
}

$o = new MyClass();

// reflect MyClass::myInstanceMethod, create a bound closure, and then unbind it
$f = (new ReflectionMethod($o, 'myInstanceMethod'))
    ->getClosure($o)
    ->bindTo(null);

// then reflect the closure
$r = new ReflectionFunction($f);

// and see it's bound to nothing, as would be the case of a static closure
var_dump($r->getClosureThis()); // NULL

So, to restate, how do you determine whether a closure is static (or more specifically, bindable) or not?

It really seems as though we should have a ReflectionFunctionAbstract::isBindable, or that ReflectionMethod::isStatic be moved up to ReflectionFunctionAbstract.

like image 601
Dan Lugg Avatar asked Jan 20 '15 14:01

Dan Lugg


2 Answers

It seems impossible now.
You can find some debates here: https://bugs.php.net/bug.php?id=64761
The only real workaround I use for myself now is adding ->isBindable property manually.

Here's some code I found here https://github.com/atoum/atoum/blob/master/classes/test/adapter/invoker.php
Maybe will give you a few ideas

protected static function isBindable(\closure $closure)
    {
        $isBindable = (version_compare(PHP_VERSION, '5.4.0') >= 0);
        if ($isBindable === true)
        {
            $reflectedClosure = new \reflectionFunction($closure);
            $isBindable = ($reflectedClosure->getClosureThis() !== null || $reflectedClosure->getClosureScopeClass() === null);
        }
        return $isBindable;
    }
like image 199
Oleg Dubas Avatar answered Oct 21 '22 21:10

Oleg Dubas


If binding works, the Closure will have a $this bound to it. So, just bind it and then check for the $this. If it's null, well, then it's a static Closure.

function isBindable(\Closure $closure) {
    $boundClosure = @\Closure::bind($closure, new stdClass);

    return $boundClosure && (new ReflectionFunction($boundClosure))->getClosureThis() != null; 
}
like image 7
bwoebi Avatar answered Oct 21 '22 21:10

bwoebi