rfc:optin_block_scoping

PHP RFC: use construct (Block Scoping)

Introduction

This RFC proposes the introduction of the use construct, a new language construct for managing temporary variables and resources. It allows developers to define a block or a statement in which one or more variables are in scope. Upon statement completion, these variables are automatically and reliably unset, decreasing their refcount and freeing the resource if it is not referenced elsewhere.

This construct addresses several recurring challenges in PHP. Developers often need to ensure that resources like file handles are properly disposed of, especially when gracefully handling exceptions for fallible operations. Additionally, controlling variable scope is a frequent source of subtle bugs, such as the classic foreach-by-reference issue, where variables leak out of loops. Manually managing this with verbose try-finally blocks or error-prone unset() calls clutters code and reduces clarity.

The use statement provides a single, elegant solution to these common problems. By adopting a feature that has proven its value in other ecosystems, like Python’s with or C#’s using, PHP can offer a safer and more readable way to write robust, maintainable code.

<?php   use ($connection = $db->pool->getConnection()) { $connection->execute('UPDATE users SET last_seen = NOW() WHERE id = ?', $id); }   // $connection is now unset and the connection is returned to the pool. assert(!isset($connection));

Proposal

We propose a new use() construct. It takes a list of variable declarations, ensures they are defined, executes the associated statement list (block) and reliably unsets the variables afterwards, no matter how the block is left. If the stored array or object is not referenced elsewhere (i.e. the unset causes the reference count to drop to zero), it will trigger the regular destruction logic, including calling the destructors of all objects that will (recursively) be freed.

This feature provides immense value, particularly for modern applications built on long-lived servers, where disciplined and immediate resource cleanup is not just a best practice, but a necessity for stability and performance. By desugaring to a try-finally block internally, it provides a zero-cost abstraction that is both safe and highly ergonomic, eliminating the need for verbose manual cleanup.

For example, the following code:

use ($a = $b, $c) /* <statement> */

Is semantically equivalent to:

try { $a = $b; $c ??= null; // If $c was not declared, it's initialized to null. /* <statement> */ } finally { unset($c); unset($a); }

This guarantees that variables are unset immediately upon exiting the scope, rather than waiting for the function to end.

The full syntax is defined as follows:

<use_statement> ::= "use" "(" <variable_declarations> ")" <statement> <variable_declarations> ::= <variable_declaration> { "," <variable_declarations> } <variable_declaration> ::= (<variable> "=" <expression>) | <variable>

Examples

Simple example:

<?php   use ($x = 10, $y) { var_dump($x); // int(10) var_dump($y); // NULL }   // Both $x and $y are now unset. // Warning: Undefined variable $y in php-wasm run script on line 10 var_dump($x); // NULL // Warning: Undefined variable $y in php-wasm run script on line 12 var_dump($y); // NULL

Example showing an edge case (solving the foreach reference bug):

<?php   $array = [1, 2, 3];   use ($value) foreach ($array as &$value) { $value *= 2; } // $value is unset here, breaking the dangerous lingering reference to the last element.   // This loop no longer accidentally modifies $array[2]. foreach ([99] as $value) {}   var_dump($array); // Correctly outputs: array(3) { [0]=> int(2) [1]=> int(4) [2]=> int(6) }

Example showing reliable resource management:

<?php   // Using the https://github.com/azjezz/psl library for an object-oriented file API. use Psl\File; use Psl\File\LockType;   function process_file(string $path): string { try { // Both $file and $lock are guaranteed to be cleaned up after the block. use ( $file = File\open_read_only($path), $lock = $file->lock(LockType::Shared), ) { // The lock ensures that the entire file contents is // read in an atomic fashion and are internally consistent, // but no longer needs to be held once we obtained the // entire contents and begin to process them. $content = $file->readAll(); }   // The file lock is released here, immediately after it is no longer needed, // which is crucial in high-concurrency environments. } catch (Exception $e) { // The file lock will also be released before handling the Exception.   sleep(10); // Simulate expensive exception handling.   throw new Exception('Processing failed', previous: $e); }   sleep(10); // Simulate expensive processing   return $content; }

Backward Incompatible Changes

None. The existing use keyword is being reused in a new context. The parser can unambiguously distinguish this construct from existing uses of use (for traits, namespace imports, and closures) based on the syntax that follows.

Proposed PHP Version(s)

Next PHP 8.x

RFC Impact

To the Ecosystem

  • IDEs, LSPs, and Static Analyzers: Will need updates to understand the new scoping rules. This will enable them to provide correct autocompletion and error analysis, correctly identifying when a variable is out of scope.
  • Auto-Formatters and Linters: Will require updates to support the new syntax.

To Existing Extensions

None.

To SAPIs

None.

Open Issues

The final behavior regarding pre-existing variables is subject to discussion on the mailing list before the vote begins. The key question is: what should happen if a variable declared in use() already exists in the outer scope?

  • Option A (Unset): The variable is shadowed inside the use block and is unconditionally unset upon exit. Any previous value is lost. This is the current implementation in the PoC.
  • Option B (Restore): The variable's original value is saved before entering the use block and restored upon exit. This would create a true block scope that does not affect the outer scope.

Future Scope

This RFC lays the foundation for explicit resource management in PHP. Future proposals could build upon it

  • Introducing a Disposable interface (similar to C#'s IDisposable) to allow objects to define custom, explicit cleanup logic that is automatically called by use.
  • Extending the use statement to support other resource management patterns.

Voting Choices

This RFC will have a single vote on whether to accept the feature in principle. The behavior for pre-existing variables (Open Issues) will be finalized based on mailing list discussion before the vote starts. A 2/3 majority is required.

Patches and Tests

A proof-of-concept implementation using the `unset` behavior is available at: https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scope

Implementation

After the RFC is implemented, this section should contain:

  • The version(s) it was merged into
  • A link to the git commit(s)
  • A link to the PHP manual entry for the feature

References

Rejected Features

None.

rfc/optin_block_scoping.txt · Last modified: by timwolla