This is an old revision of the document!
PHP RFC: Support Closures in constant expressions
- Version: 1.0
- Date: 2024-10-24
- Author: Tim Düsterhus, tim@tideways-gmbh.com
- Status: Draft
- First Published at: https://wiki.php.net/rfc/closures_in_const_expr
Introduction
Proposal
It shall be legal to include Closures in constant expressions. This includes:
- Attribute parameters.
- Default values of properties, parameters, and static variables.
- Constants and Class Constants.
Constraints
Closures placed in constant expressions are subject to the following constraints:
- They must not include variables from the surrounding scope using
use($foo, $bar)
, because except for constants and static variables there is no surrounding scope. This also means that short closures (arrow functions) are not supported, because they perform implicit capturing. - They must be
static
(and thus they must not access$this
). Semantically$this
would only be well-defined in default values for properties and possibly attribute parameters, but this would require reevaluating the Closure for each object / attribute instance, which would be different to existing constant expressions which are only evaluated once. For this reason this is left to future scope.
Scoping
As with other constant-expressions, Closures defined in constant expressions follow the expected scoping rules of the context they are placed in. This means that Closures in property default values may access private
properties, methods, and class constants of the class where they are defined, similarly to how a Closure defined in the constructor and stored in a property may access those private members. Likewise are Closures in attribute parameters allowed to access private members of the surrounding class.
Use Cases
Custom field validation for an attribute-based object validation library:
final class Locale { #[Validator\Custom(static function (string $languageCode): bool { return \preg_match('/^[a-z][a-z]$/', $languageCode); })] public string $languageCode; }
Testcase generation for a testing library:
final class CalculatorTest { #[Test\CaseGenerator(static function (): iterable { for ($i = -10; $i <= 10; $i++) { yield [$i, $i, 0]; yield [$i, 0, $i]; yield [0, $i, ($i * -1)]; } })] public function testSubtraction(int $minuend, float $subtrahend, int $result) { \assert(Calculator::subtract($minuend, $subtrahend) === $result); } }
Backward Incompatible Changes
None. Placing Closures into constant-expressions previously resulted in a compile-time error.
Nevertheless, as with every RFC that changes what previously was a compile-time error to be valid PHP code, this RFC requires changes to static analyzers and IDEs to correctly understand the semantics of the code and not erroneously report errors.
Proposed PHP Version(s)
Next PHP 8.x (8.5).
RFC Impact
To SAPIs
None.
To Existing Extensions
None.
To Opcache
Opcache needs to be adjusted to correctly store Closures in constant expressions in SHM. The PoC PR includes the necessary Opcache changes and passes all tests with Opcache / JIT enabled.
New Constants
None.
php.ini Defaults
None.
Open Issues
n/a
Unaffected PHP Functionality
Only constant expression are affected by the change and only in a way that Closure objects may appear in places where they previously could not appear in (e.g. class constants).
Future Scope
- Support non-static Closures.
- Support first-class callables.
Proposed Voting Choices
Patches and Tests
Implementation
n/a
References
Rejected Features
n/a