DEV Community

david duymelinck
david duymelinck

Posted on

Invokable class or wrapped closure?

I recently witten a post about currying and during my research one of the examples that AI gave me was this.

function taxedAmount(int|float $rate)
{
   return fn(int|float $amount) => $amount * (1 + $rate / 100);
}

$twentyProcent = taxedAmount(20); 
$priceWithTax = $twentyProcent(100);
Enter fullscreen mode Exit fullscreen mode

And to me that looked a lot like an invokable class.

class TaxedAmount
{
   public function __construct(private int|float $rate)
   {}

   public function __invoke(int|float $amount)
   {
      return $amount * (1 + $this->rate / 100)
   } 
}

$instance = new TaxedAmount(20);
$priceWithTax = $instance(100); 
Enter fullscreen mode Exit fullscreen mode

In the PHP frameworks there are several places that execute invokable classes. The first things that come to mind are controllers and events.
I'm wondering what the experience is to use a wrapped closure instead of an invokable class?

Testing a Laravel controller

Setup

In composer.json I added

"autoload": {
  "files": [ "app/functions.php" ]
}
Enter fullscreen mode Exit fullscreen mode

The functions.php contains

$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__.'/Functions'));

foreach ($iterator as $item) {
    if ($item->isFile() && $item->getExtension() === 'php') {
        include $item->getPathname();
    }
}
Enter fullscreen mode Exit fullscreen mode

Creating the route

In the app/Functions/Controllers directory I added closureController.php.

namespace App\Http\Controllers; // fake it till you make it

use Illuminate\Http\Request;

function closure() {
    return fn(Request $request) => $request->uri()->path();
}
Enter fullscreen mode Exit fullscreen mode

In routes/web.php I added, Route::get('closure', \App\Http\Controllers\closure());. And I got closure as response in the browser

Why did it work?

I discovered that the Laravel router checks if the action is a closure or not. And if it is the closure will get executed.

Other places in Laravel where you could use a wrapped closure

Any place where the framework accepts anonymus functions. I'm looking at the ServiceProvider child classes where anonymus functions are used frequently.

Conclusion

Because I used arrow functions in my wrapped closure functions it seems like they require less typing. But if you use regular functions the typing will be almost the same.

The wrapped closure function won't allow you to autowire dependencies. The only time I go for constructor dependencies using an invokalbe class is when they are for a private method.

The only benefit of wrapped functions is that they can be placed in a single purpose file, and this makes them testable.
But you need the extra setup for the functions to be discoverable.

While it was a fun idea to entertain, I don't think I'm going to use wrapped closures a lot.

Top comments (0)