DEV Community

Jarosław Szutkowski
Jarosław Szutkowski

Posted on • Edited on

Static vs Non-Static Closures in PHP – A Surprising Benchmark

When PHP Inspections suggested to change my anonymous function in array_map to a static one, I thought: why bother? 🤷‍♂️ It seemed like a small style preference – not something that would really matter.

But I got curious. 🔍 Could adding static to a function actually make your code faster or use less memory? That simple question led me down a rabbit hole 🕳️ of Pull Requests, Stack Overflow threads, and performance benchmarks.

This blog post tells the story of what I found – and why using static might be a small habit that makes a big difference. 💡

Pull Request That Started It All

While digging around, I found a GitHub Pull Request in the Ocramius/GeneratedHydrator repository. In it, the author replaced normal anonymous functions with static ones – and claimed the change resulted in around 15% faster execution. ⚡

This wasn’t a micro-optimisation just for fun. According to the author, removing access to $this (which static closures do by default) helped PHP handle memory and execution more efficiently. 🧠

It was the first sign that this so-called "style suggestion" might actually lead to meaningful performance improvements. 🚀

Why $this Matters in Closures

Digging deeper, I found a great explanation on Stack Overflow. It turns out that one key difference with static closures is that they do not bind to the current object context — meaning they don’t carry around a reference to $this. 🧩

Why does that matter?

"The closure holding a reference to $this might prevent garbage collection of that object, which in turn may also impact performance significantly."
Stack Overflow

In simpler terms: if a closure keeps a reference to its parent object, PHP might not free up that memory as quickly. 🐘 Over time, this can slow things down and eat up resources — especially if you’re creating a lot of closures in a loop.

That’s exactly what the next example demonstrates. 🔬

A Simple Benchmark That Proves the Point

To see the difference for myself, I recreated a script shared on Stack Overflow. It creates many instances of a class and stores closures returned by a method — all without needing $this. 🛠️

Original source: Stack Overflow

class LargeObject {
    protected $array;

    public function __construct() {
        $this->array = array_fill(0, 2000, 17);
    }

    public function getItemProcessor(): Closure {
        return static function () {
            // some logic that doesn’t use $this
        };
    }
}

$start = microtime(true);

$processors = [];
for ($i = 0; $i < 2000; $i++) {
    $lo = new LargeObject();
    $processors[] = $lo->getItemProcessor();
}

$memory = memory_get_usage() >> 20;
$time = (microtime(true) - $start) * 1000;
printf("This took %dms and %dMB of memory\n", $time, $memory);
Enter fullscreen mode Exit fullscreen mode

I ran this with and without the static keyword using PHP 8.4 and measured the results with PHPBench. 📊 Here’s what I got:

+----------------------------+-----+-------------+---------+---------+
| subject                    | its | memory_real | mode    | mean    |
+----------------------------+-----+-------------+---------+---------+
| benchUsingStaticKeyword    | 100 | 2.10mb      | 3.69ms  | 3.70ms  |
| benchNotUsingStaticKeyword | 100 | 71.30mb     | 17.03ms | 17.17ms |
+----------------------------+-----+-------------+---------+---------+
Enter fullscreen mode Exit fullscreen mode

That’s not a small gap – that’s more than 30x more memory and over 4x longer execution time without static. 😳

This convinced me: using static where $this isn’t needed is not just a style thing. It’s a performance win. 🏆

Automating It with Rector

After seeing how much of a difference static can make, I started looking through my own project. 🧑‍💻 There were many closures that didn’t use $this and could be made static. But going through them one by one? That would take ages. 😩

So I turned to an old favourite: Rector – a tool for automated PHP refactoring. 🤖

It turns out Rector already has two rules that do exactly what I needed:

I added them to my Rector config and ran the tool on the whole codebase. It worked beautifully – all eligible closures were made static automatically. ✨

To make this a permanent habit, I left the rules in the config. Now, every Pull Request runs Rector in dry-run mode and fails if someone forgets to use static where it’s possible. 🔄

✅ Performance boost? Check.
✅ No manual refactoring? Check.
✅ Team-wide consistency? Check.

Conclusion

What started as a minor suggestion from a code analysis tool turned into a valuable lesson in PHP performance. 📚

By switching to static closures when $this isn't needed, you:

  • avoid holding unnecessary references, 🚫
  • help PHP free memory more effectively, 🧹
  • and get faster execution times. ⏱️

It’s a simple habit with real benefits – and thanks to tools like Rector, it’s easy to make it stick. 🧰

Next time you’re writing a closure, stop and ask: do I need $this here? If not – make it static. ✅

Top comments (3)

Collapse
 
xwero profile image
david duymelinck • Edited

It is in the PHP documentation, go here and just scroll up.

I think it is a strange default. Like you mentioned there are array but also sort functions that have a callable as an argument. Functions like that will never need the binding of the parent class.
Also using $this in a function is just weird, it is a function not a method.

PS: remove the emojis, they make reading the text more difficult. There are some that make no sense.

Collapse
 
jszutkowski profile image
Jarosław Szutkowski

Hi David,

Thanks so much for pointing out the relevant PHP documentation!

On the second part of your comment - I think it really depends on the context. If things get more complex, it can make sense to extract the logic into a separate method or service. In those cases, using a non-static function might actually be the better option

Collapse
 
xwero profile image
david duymelinck • Edited

For an arrow function I get the binding, because there the outside variables can be used.
But for an anonymus function use is needed to get outside variables into the function.
It is inconsistent behaviour.