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.