PHP Closures - Getting class name of closure scope origin

Viewed 492

Case

I am playing around on a laravel project to see if i can use closures for my implementation of a sorting interface, and i noticed that when i dd() my closure, it also shows the class in which the closure was created as a property.

Minimised Code

// in my Order model class, i have a function that will return a closure
public static function defaultSortFunction(){
    $sortColumn = property_exists(self::class,'defaultSortingColumn') ? self::$defaultSortingColumn : 'created_at';

    return function($p,$n)use($sortColumn){
        return $p->$sortColumn <=> $n->$sortColumn;
    };
}
// in one of my controller I use for testing, I added these 2 methods for testing
public function index(){
    $sortFunction = Order::defaultSortFunction();
    $this->someOtherFunction($sortFunction);
    return 'done';
}

private function someOtherFunction($fn){
    dd($fn);

    // $scopeModel = get_class($fn); => Closure
    
    // example of how I can use this value later
    // $scopeModel::take(10)->get()->sort($fn);
}

The result of the dd() inside someOtherFunction():

^ Closure($p, $n) {#1308 ▼
  class: "App\Order"
  use: {▼
    $sortColumn: "created_at"
  }
}

Question

From the result of the dd() it shows that the closure has a property that shows that it was defined in the class App\Order. Is there any way to access this value?

I have tried get_class($fn) but as expected it gives "Closure", and if i did $fn->class it gives an error saying Closure object cannot have properties.

2 Answers

You may use Reflection API on your closure which is a much cleaner way than debug_backtrace

// in one of my controller I use for testing, I added these 2 methods for testing
public function index(){
    $sortFunction = Order::defaultSortFunction();
    $this->someOtherFunction($sortFunction);
    return 'done';
}

private function someOtherFunction($fn){
    $reflectionClosure = new \ReflectionFunction($fn);
    dd($reflectionClosure->getClosureScopeClass()->getName());
}

getClosureScopeClass returns a ReflectionClass instance based on the class you need to find and getName finishes the job.

You can of course inject the class name in to the closure via a parameter in your defaultSortFunction, but that's obviously not so nice.

You should be able extract the calling class yourself from the call stack using: https://www.php.net/manual/en/function.debug-backtrace.php

If you use the limit parameter you should be able to restrict it to only returning the calling class and no further back.

I don't know for sure, but I suspect it isn't particularly performant.

Related