The Magical invoke Method in PHP. Your Clean Code Ally

PHP magic methods make a programmer's life easier. Today, I'll explain why the __invoke magic method is your ally in many cases and will allow you to write more readable and maintainable code. And I'll demonstrate it with examples.
The Magical invoke Method in PHP. Your Clean Code Ally

The __invoke method is part of PHP's magic methods and has been available since version 5.3, a couple of years ago. However, while other magic methods (like toString) are widely used, __invoke is not often seen in other people's code. I guess that's because of a general lack of awareness 🤔. Let's try to change that; allow me to introduce you to some extremely useful cases for the __invoke method and why it's tremendously handy when you want to write clean code (clean code, if you're not familiar, is essentially about understanding code at a glance without thinking "WTF?...").

What is __invoke in PHP

The __invoke method is called every time an object is called as if it were a function. In other words, this method is called every time we write something like this...

<?php
class GreetClass
{
    public function __invoke($x)
    {
        return "Hello $x"
    }
}
$obj = new GreetClass;
$obj("me");

What we are doing here is creating a default method that gets called when we treat the PHP object as if it were a function. Using the syntax $object() triggers the __invoke method.

This primarily allows us two things. On the one hand, it forces us to follow the S in SOLID. After all, if a class has only one responsibility, why have more than one public method? Moreover, if the class has only one responsibility, we define only the public method as invoke. On the other hand, it reduces the amount of code we read and, therefore, improves readability. Notice that we no longer need to use the general syntax of $object->method().

In this way, we only need to come up with a good name for the class, which clearly defines what it does. There is no longer a need to also find a name for the function (which will ultimately be redundant).

Using a Function or Using __invoke

Let's start with the premise that, according to SOLID and more specifically the S (Single Responsibility Principle), each unit of code should have only one responsibility. This means that a class or function should solve a single task. For example, if we need to calculate a value and then, based on that value, send an email, there are really three associated classes or functions: one to get the data, one to process it, and one to send the email.

Imagine that we need to perform a process that repeats throughout our code, a relatively simple process like obtaining a value based on another. Let's take for example determining whether a user is a resident of Madrid or not based on a postal code (which, for simplicity, is stored as a string).

Returning to our case, we could have a function or closure like this:

function livesInMadrid(User $user) {
  return substr($user->postalCode, 0, 2) === "28";
}

And this, in itself, is not bad; it's a good practical example. Given that the previous code has no dependencies and we can calculate the desired value based on the object passed as a parameter, it simply works ¯_(ツ)_/¯.

However, what if, during the process, we needed some dependency? Let's say now we need a function to determine if a user is of legal age based on their age. As (I insist) we try to be good programmers and since our application may be used in multiple countries, we shouldn't use a literal and perform a comparison with 18 years (legal age in Spain). Therefore, we need a comparer. This way, we'll pass the age to our comparer, and it (based on the user's location, for example) will calculate if they are of legal age.

It could look something like this:

function isOfLegalAge(LegalAgeValidator $validator, User $user): bool {
    return $validator->validateLegalAge($user->age);
}

It's not quite elegant. If we have to instantiate the dependency of our function or class outside of the entity itself, it breaks encapsulation and abstraction. It's the user who has to instantiate the comparer outside of the code unit, and we are delegating the responsibility to them.

This happens because PHP functions or methods do not support external dependency injection as such. Therefore, we would have to resort to a class (or a closure, which along with the functor, would allow external dependency injection but would break our abstraction and encapsulation).

Therefore, in functions, we cannot use the __invoke method or anything similar, as it's a magic method applied only to objects; instances of classes. While our code is simple and has no dependencies, we can use a function. If, on the other hand, it does have dependencies, we should not use functions.

Using a Class with __invoke

When a function can't do something, we can always resort to a class. We create a class that defines a constructor in which our validator is injected. Inside this class, named LegalAgeValidator, we define a function that takes the $user object. This public function would be... almost the same as the class name? validateLegalAge() perhaps? And to invoke it, you would write

function ... (LegalAgeValidator $validator) {
    $validator->validateLegalAge(x)
}

We are writing the same concept twice, once for the class and once for the method. When you choose a good name for the class, it's practically self-explanatory. This would be simpler and more readable with the invoke method.

function .... (LegalAgeValidator $validator) {
    $validator(x)
}

Let's analyze the advantages of the previous code.

  • It's easy to read. Without diving into how the class works internally, we know that it validates that something or someone has the legal age.
  • In addition to this, we don't have to look at the class's documentation to see which public method we can use. We always use invoke.
  • If you get used to using invoke, it will become increasingly difficult for you to declare multiple methods per class (which ultimately leads to the dreaded XManager or XServices classes that aggregate 2000 functions).
  • Along with Solid and CleanCode, your tests will also be more readable.

When do I need to use it with $this?

class X() {
    public function __construct(InvokeClass $x) {
        $this->$x = $x;
    }

    public function myMethod() {
      // How do I call the __invoke of InvokeClass x?
      $this->x(); // ❌

      // Better this way...
      $this->x->__invoke(); // ✅
    }
}

The first few times you use the magic __invoke method of class A and have to use it in another class B (because you have injected it as a dependency), you might have a question: How do I invoke it? Normally, we use $this->something->method(), but does it work the same way with invoke?

When we inject a class with __invoke defined, we can use it like $object(), but when we need to call it with this, it doesn't work exactly the same way.

If we use $this->x(), PHP will try to find a method called x in the current class and won't find it. Somehow, we need to let it know that we are not looking for a method in the current class but rather calling the magic __invoke method of another class. We achieve this with:

$this->x->__invoke($params);

Conclusion: __invoke is here to help

As I mentioned at the beginning of this article, I haven't seen the __invoke method used as frequently in other programmers' code as methods like __construct or __toString. It's possible that some of them were not aware of this useful method. However, since I've become familiar with it, I try to use it whenever possible due to the advantages I've discussed throughout this document.

I hope I've convinced you that the magic __invoke method has been available since PHP 5.3 to make our lives easier and improve code readability. After all, all you need to do is come up with a good class name. Remember that in computer science, there are only two really hard things: naming things well and invalidating cache - why bother thinking of two good names when you can think of just one?