Stop using old-fashioned closures in modern PHP. There are 4* ways to replace them.

Vlad Reshetylo
5 min readJan 25, 2024

--

Somewhere in Magento 2 source code. Can you understand what is going on from the first sight? Me neither.

Yeah, I know that it sounds too provocative 😄
Like “What? Why? What instead?”

But don’t worry, and let’s look at everything in order.

So, closures. We got that possibility in PHP a long time ago, in 2014. It’s a handy way to define an anonymous function and immediately pass it if we don’t need to reuse it.

What are the most popular use cases of Closures? It’s transforming and filtering. Of course, I mean array_map and array_filter functions, but it’s not the only cases. It’s just the most obvious example.

Maybe you want to ask — what’s wrong with Closures? Nothing, in the case of correct usage.

So let’s go back to the topic title image. It’s a code from a real project. I found it somewhere in a Magento 2 source code on GitHub. And it’s an excellent example of closure that looks bad. It’s simple filtering and transforming logic, but it’s hard to understand at first sight.

Readability is awful, testing will be complicated, and all this is because of incorrect closure usage.

Should we stop using closures because of that? Actually, yes and no. Shortly, it depends on the closure’s complexity and logic’s obviousness.

It’s easier to explain with examples.

For example, we have an imaginary array of users and want to filter only active users. In this case, our closure will be pretty simple, and there is no need to overcomplicate that.

simple and clear

Readability is excellent; there is nothing to say.

Now, let’s imagine that we pulled a massive list of users from a Database and want to filter users who can receive an email, an SMS, etc. And we have the following rules for sending emails:
- user should be active
- the user should have an email
- the user should not have the “email is invalid” checkbox
- the user’s country, for example, is the USA (let’s imagine that there is a legal requirement for that)

Yeah, this example is somewhat imaginary because usually we can do filtering on the stage of a database query, but it’s pretty close to real cases, so let’s imagine that it’s the only way we have

Now we have something like this:

It doesn’t look good. Yes, it’s still readable enough (because it’s still relatively simple logic), but this code shows us only WHAT we do and doesn’t show WHY.

A new developer will definitely ask us, “Why should we filter non-US users?” because it’s not explained by code.

Yes, we can add a comment here, but every time you add the comment, you should ask yourself if your code can be improved to avoid that.

And that is the case where closure should not be used. There are four ways to replace it:

0. Pass function name as a string (the worst one) 🤢

🤮

It’s horrible, and please don’t do that in modern PHP. I will not even describe the Pros and Cons in that way. Just don’t.

1. Assign the closure to the variable. 🤦

🤦

Pros:

  • variable name can describe the logic inside

Cons:

  • it still looks messy
  • filtering logic cannot be tested separately
  • filtering logic cannot be reused outside the method
  • the single-responsibility principle is violated in the case of the complex logic
  • we need to use “static function” in case we need to cache smth inside the closure

Generally, it can be better than a bare closure, but not dramatically. I don’t recommend using this way.

2. Use First-class Callable Syntax 👍

Starting from PHP8.1, we can pass class methods as a callable. And it’s an excellent opportunity for us!

🥰

Pros:

  • method name describes the logic itself
  • the method can be tested separately
  • the method can be reused

Cons:

  • the single-responsibility principle is violated in case of the complex logic

Generally, it’s much, much better than the previous option. It’s an excellent middle ground in most cases.

3. Use invokable classes 🔥

In my opinion, that’s the most powerful, readable, and flexible method.

Pros:

  • We can define something like a FilterInterface, in case we have plenty of them. It’s a win because, unfortunately, we cannot define the Closure shape (like function(Closure<array><bool> $callback)) in PHP, but we can do that for __invoke method
  • Filter can be tested outside the class and can be reused outside the class
  • The single-responsibility principle is not violated
  • Can be reused as a singleton to avoid usage of “static function”

Cons:

  • little bit verbose, no sense to use for a simple logic

Generally, it’s a great way to extract complex logic and make it testable and reusable. This way reveals its potential to max in case a few similar closures that can replace each other by condition.

For another example, here is a simplified example of array_map usage with different transforming methods

This example shows how nicely we can define type hinting for our transforming methods, unlike closures, where we cannot do that.

In summary:

  • Use arrow-syntax closures for one-liner logic.
  • Employ First-class Callable Syntax for complex logic within the current scope.
  • Use for invokable classes when dealing with numerous closures sharing the same purpose but varying logic. This approach ensures testability and maintainability.

If you get a chance to test this advices, and find success, or you are just going to do that — kindly share your experience. I’m genuinely interested in receiving feedback on how well this recommendation performs in different scenarios beyond my own. 🙂

--

--