Get "PHP 8 in a Nuthshell" (Now with PHP 8.4)
Amit Merchant

Amit Merchant

A blog on PHP, JavaScript, and more

A closer look at Invokable classes in PHP

In PHP, Invokables refer to any class that may be instantiated without any constructor arguments. In other words, one should be able to create an instance solely be calling new $className(). To implement an invokable class, one needs to use __invoke() magic method of PHP. Before we understand how invokable exactly works, let’s take a look why invokable classes even exists in PHP.

PHP does not allow the passing of function pointers like other languages. Functions are not first class in PHP. Functions being first class mainly means that you can perform operations on functions which includes being passed as an argument, returned from a function, modified, and assigned to a variable. Using __invoke() method PHP can accommodate pseudo-first-class functions. The method can be used to pass a class that can act as a closure or a continuation, or simply as a function that you can pass around.

How to create an Invokable class?

An invokable class can be created by implementing a __invoke magic method into it. Below is a simple example of an invokable class.

function sparkles(Callable $func) {
  $func();
  return "fairy dust";
}
 
class Butterfly {
  public function __invoke() {
    echo "flutter";
  }
}
 
$bob = new Butterfly();
echo sparkles($bob); // flutterfairy dust

As you can see, class Butterfly is an invokalble class which can be callable by any function. In this case, we’ve passed the object of the class Butterfly directly to the sparkle method as an argument which is of type Callable. And this when _invoke method will be called.

You may also like: This is why PHP don’t have multiple inheritance

Usecases for Invokable classes

As I discussed earlier, Invokables in PHP are designed to compensate for PHP’s lack of first class function, so by creating an invokable object, you are essentially creating a first class function. An ideal scenario of invokable object is that it consists of only a constructor and an __invoke() method in its public interface, and it’s sole responsibility is to serve a first class function.

Invokables are also useful when you don’t know or don’t want to know the implementation of the object you are receiving. You simply want to execute it without knowing anything about it. For instance take this very good example which I found on StackOverflow.

Lets say you want to sort the following array:

$arr = [
    ['key' => 3, 'value' => 10, 'weight' => 100], 
    ['key' => 5, 'value' => 10, 'weight' => 50], 
    ['key' => 2, 'value' => 3, 'weight' => 0], 
    ['key' => 4, 'value' => 2, 'weight' => 400], 
    ['key' => 1, 'value' => 9, 'weight' => 150]
];

Now, if we want to sort this associative array using 'value' key, we can use usort function which allows you to sort the array using a custom function.

$comparisonFn = function($a, $b) {
    return $a['value'] < $b['value'] ? -1 : ($a['value'] > $b['value'] ? 1 : 0);
};
usort($arr, $comparisonFn);

// ['key' => 4, 'value' => 2, 'weight' => 400] will be the first element, 
// ['key' => 2, 'value' => 3, 'weight' => 0] will be the second, etc

Now, if we want to sort the same array using the 'key' key, you need to rewrite the function in order to do so.

$comparisonFn = function($a, $b) {
    return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0);
};
usort($arr, $comparisonFn);

// ['key' => 1, 'value' => 9, 'weight' => 150] will be the first element, 
// ['key' => 2, 'value' => 3, 'weight' => 0] will be the second, etc

As you can see the logic of the function is identical to the previous one, however we can’t reuse the previous due to the necessity of sorting with a different key. This problem can be addressed with a class that encapsulates the logic of comparison in the __invoke method and that define the key to be used in its constructor:

class Comparator {
    protected $key;

    public function __construct($key) {
            $this->key = $key;
    }

    public function __invoke($a, $b) {
            return $a[$this->key] < $b[$this->key] ? 
               -1 : ($a[$this->key] > $b[$this->key] ? 1 : 0);
    }
}

A Class object that implements __invoke is called a “callable” and it can be used in any context that a function could be, so now we can simply instantiate Comparator objects and pass them to the usort function:

usort($arr, new Comparator('key')); // sort by 'key'

usort($arr, new Comparator('value')); // sort by 'value'

usort($arr, new Comparator('weight')); // sort by 'weight'

A real world example of Invokables

A practical use of Invokables we can find in Laravel where you can pass the invokable objects to Laravel’s scheduler like this.

<?php
namespace App\Console;

use Illuminate\Support\Facades\DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(new DeleteRecentUsers)->daily();
    }
}

Here, DeleteRecentUsers can be a invokable class object which can used to delete recent users.

Learn the fundamentals of PHP 8 (including 8.1, 8.2, 8.3, and 8.4), the latest version of PHP, and how to use it today with my new book PHP 8 in a Nutshell. It's a no-fluff and easy-to-digest guide to the latest features and nitty-gritty details of PHP 8. So, if you're looking for a quick and easy way to PHP 8, this is the book for you.

Like this article?

Buy me a coffee

👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.

Comments?