Skip to content
Tech News
← Back to articles

PHP 8.6 Closure Optimizations

read original get PHP 8.6 Closure Optimization Guide → more articles
Why This Matters

PHP 8.6 introduces closure optimizations that cache stateless closures and convert non-static closures into static ones when safe, reducing memory leaks and improving performance. These enhancements can lead to more efficient code execution and less reliance on PHP's cycle collector, benefiting both developers and end-users by enabling faster, more reliable applications.

Key Takeaways

Stateless closures, i.e. those that are static , don't capture any variables and don't declare any static variables, are cached between uses.

Non-static closures are turned into static ones if they are guaranteed not to make use of $this .

This RFC proposes two new optimizations for closures (including arrow functions) that come with some theoretical BC breaks. The purpose of this RFC is to evaluate whether these BC breaks are an acceptable trade-off for the gained performance.

This optimization will attempt to infer static for closures that are guaranteed not to make any use of $this .

class Foo { public $closure ; public function __construct ( ) { $this -> closure = function ( ) { echo "Hello world!" ; } ; // Or $this -> closure = fn ( $a , $b ) => $a + $b ; } }

Previously, the closure in __construct would have implicitly captured $this , keeping the instance of Foo alive for the lifetime of the closure. Conversely, the instance of Foo would keep the closure alive, creating a reference cycle that requires running PHP's cycle collector to resolve. Frequently, such cycles are not resolved for the remainder of the request, given the cycle collector often doesn't run at all. These cycles can also make it more likely for the cycle collector to run in the first place, spending time on resolving a cycle that didn't need to exist in the first place.

The aforementioned optimization will attempt to infer static for closures that fulfill the following (slightly esoteric) conditions. The closure must not:

use $this . That's the obvious case. use $$var , given $var could refer to 'this '. use Foo::bar() , given this could be a hidden instance call to a (grand-)parent method. use $f() , for the same reason as 3. use call_user_func() , for the same reason as 3. declare another (uninferable) non-static closure, where $this flows from parent to child. use require , include or eval , given the called code might do any of the above.

These rules appear to be quite effective. A test was performed on Symfony Demo, where static modifiers were removed from all closures. The optimization was able to infer 68/87 (~78%) closures that were explicitly marked as static .