Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been
working on: "use construct (Block Scoping)".
We wanted to raise a few initial points:
The RFC proposes the use keyword. What are your thoughts on a new using keyword instead, similar to C# or Hack?
How do you feel about the questions raised in the "Open Issues" section?
What are your general thoughts on the RFC?
Please find the following resources for your reference:
RFC: https://wiki.php.net/rfc/optin_block_scoping
POC:
https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scope
Thanks,
Seifeddine Gmati.
We wanted to raise a few initial points:
The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?
I’m undecided on this. However, using might be easier to understand since use could be confused with the existing keyword.
How do you feel about the questions raised in the "Open Issues" section?
I prefer Option B for restoring the value.
What are your general thoughts on the RFC?
In general, I support this RFC. I’d like to see examples from other programming languages, though. You mention C# and Hack above. Can you elaborate in the RFC on how they implement this functionality? What about other programming languages? I know some (Rust maybe?) are scoped to blocks by default.
Cheers,
Ben
Hi
Am 2025-11-04 03:08, schrieb Ben Ramsey:
In general, I support this RFC. I’d like to see examples from other
programming languages, though. You mention C# and Hack above. Can you
elaborate in the RFC on how they implement this functionality? What
about other programming languages? I know some (Rust maybe?) are scoped
to blocks by default.
Please have a look at Seifeddine's previous reply to Edmond
(https://news-web.php.net/php.internals/129076) and my reply to Arnaud
that I just sent (https://news-web.php.net/php.internals/129087).
There are two things to consider when comparing against other
programming languages. PHP's semantics do not exactly fit any of these,
which means that transferring some concept from another programming
language directly does not work.
- Scoping:
Many programming languages with explicit variable declarations are block
scoped. This includes Rust (which you correctly mentioned), but also C,
C++, Java, JavaScript (with let and const). Some of them allow shadowing
variables from the scope and some don't.
- Handling of Lifetimes:
As I mentioned in my reply to Arnaud, PHP is pretty unique in the list
of programming languages with automated memory management in that it
does not primarily use an “unpredictable” garbage collector for memory
management, but instead uses reference counting with reliable destructor
semantics (that are documented). This is different from e.g. Java where
the so-called “finalizers” run at an unpredictable point in time when
the GC feels like cleaning up an object. PHP's semantics are close to
those of C++ (where this kind of memory management is called RAII) or
Rust.
For this reason, PHP just needs some generic “syntactic sugar” for
unset() that is compatible with all existing functionality using
__destruct() for those RAII semantics.
Best regards
Tim Düsterhus
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been working on: "use construct (Block Scoping)".
We wanted to raise a few initial points:
The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?
I think the three existing meanings of use are enough. A new keyword would be better.
But, if a new keyword, why not scoped?
How do you feel about the questions raised in the "Open Issues" section?
B feels more intuitive.
Cheers
Nick
Hi
Am 2025-11-04 06:16, schrieb Nick:
I think the three existing meanings of
useare enough. A new keyword
would be better.
But, if a new keyword, why notscoped?
Thank for the keyword suggestion. Personally I could also imagine
let(), possibly combined with an in:
let ($x = 10, $y) in {
var_dump($x); // int(10)
var_dump($y); // `NULL`
$z = 20;
}
Best regards
Tim Düsterhus
What would happen to the variables introduced in the statements
block, rather than in use()? Will they still be available outside of the
block?
use () {
$number = 42;
}
var_dump($number); // what happens here?
Also, is an empty list of vars in use (as in the example above) allowed?
--
Best regards,
Bruce Weirdan mailto:
weirdan@gmail.com
Hi
Am 2025-11-04 07:08, schrieb Bruce Weirdan:
What would happen to the variables introduced in the statements
block, rather than inuse()? Will they still be available outside of
the
block?
Yes. The construct only affects the variables listed in the “declaration
list”. I have adjusted the “simple example” to introduce a new variable
$z that is initially defined in the block, but not listed in the
declaration list to make that clearer.
use () { $number = 42; } var_dump($number); // what happens here?Also, is an empty list of vars in
use(as in the example above)
allowed?
That is a syntax error (unexpected )).
Best regards
Tim Düsterhus
Hello all!
Thank you for the RFC, it has been missing for many years.
If I understand correctly, are you proposing to call unset at the
end of the block?
I see that the Future Scope section mentions Disposable.
But if your goal is to introduce behavior based on Disposable,
wouldn’t that conflict with the logic of the current RFC?
I see a clear pitfall here.
If you accept this RFC with the unset operation, you will later need
a new keyword for Disposal, because these are two entirely different
scenarios.
(Should I explain why?)
In this context, I also see a problem, as if the RFC is trying to
introduce two different features into the language:
-
Scope – a visibility area. It is the scope that has the
unsetlogic. - Using – a guaranteed call of a disposal function.
Because of this, logical issues are likely to arise. If the RFC’s goal
is unclear and it tries to cover both tasks, the solution risks losing
its clarity.
P.S.
Regarding the questions in the Open Issues, option A seems to have
more explicit behavior than option B.
Best Regards, Ed
Hello all!
Thank you for the RFC, it has been missing for many years.
If I understand correctly, are you proposing to callunsetat the
end of the block?I see that the Future Scope section mentions
Disposable.
But if your goal is to introduce behavior based onDisposable,
wouldn’t that conflict with the logic of the current RFC?
I see a clear pitfall here.If you accept this RFC with the
unsetoperation, you will later need
a new keyword forDisposal, because these are two entirely different
scenarios.
(Should I explain why?)In this context, I also see a problem, as if the RFC is trying to
introduce two different features into the language:
- Scope – a visibility area. It is the scope that has the
unsetlogic.- Using – a guaranteed call of a disposal function.
Because of this, logical issues are likely to arise. If the RFC’s goal
is unclear and it tries to cover both tasks, the solution risks losing
its clarity.P.S.
Regarding the questions in the Open Issues, option A seems to have
more explicit behavior than option B.
Best Regards, Ed
Hello,
Thank you for the feedback.
Regarding the Disposable interface: introducing it in the future
won't require new syntax. The use construct can work with both
__destruct (current behavior) and a future disposal interface.
The idea is to introduce an interface like:
interface Disposable {
public function dispose(?Throwable $throwable = null): void;
}
With use:
use ($foo = new Something()) {
// work
} // ->dispose(null) called on success, ->dispose($exception) on failure
This mimics Python's context manager protocol. The dispose() method
would be called before __destruct, allowing objects to distinguish
between successful completion and failure.
However, there's nothing stopping us from shipping without it. The
lock example in the RFC doesn't need this, locks are freed
automatically in __destruct. The initial version relying solely on
__destruct works fine for most use cases.
On the name "Disposable": I'm not really a fan of this name
myself. It was just my initial thinking when trying to copy Hack. We
can come up with something better later.
On Hack's approach: Hack has a Disposable interface, but it
works differently: disposable objects must maintain refcount=1, can't
be assigned to properties, and functions returning disposables need
<<__ReturnDisposable>>. This is enforced statically by their
analyzer. At runtime, they're just regular PHP objects. We can't
replicate this in PHP.
The refcount problem: A disposal interface has its issues. Once
dispose() is called, there may still be references to the object
somewhere, leaving it in an undesirable state, unless we add a way to
enforce refcount = 1.
A disposal interface without exception awareness brings limited value.
But with ?Throwable, it becomes useful:
use ($transaction = $ctx->beginTransaction()) {
// work
} // Transaction::dispose(?Throwable) called, then __destruct
Without dispose(?Throwable), the transaction can't know whether to
commit or rollback, __destruct alone can't distinguish success from
failure.
The key point: the initial version (relying on __destruct) and a
future disposal interface don't conflict.
Thanks,
Seifeddine Gmati.
Hello
A disposal interface has its issues.
Although Arnaud Le Blanc has already covered this topic thoroughly,
I’d like to approach it from a slightly different angle.
Let’s not think of enter/exit + RefCount as a problem. Every
approach has its own purpose. In this case, there is a clear
distinction between Scope logic and enter/exit logic. These
are two different concepts, and you cannot and should not try to
satisfy both RefCount and enter/exit conditions at the same
time.
The purpose of enter/exit is to handle the try-catch-finally
pattern for a resource regardless of the reference count. Therefore,
RefCount is not an issue.
(although PHP can automatically issue a warning when attempting to
call the method while the reference count is greater than one)
If PHP applies unset or enter/exit depending on whether an
interface is implemented, it will introduce hidden behavior in the
code, making it harder for developers to understand what is happening.
Compare the two cases:
// I know for sure that Scope implements the interface
// required to be used with "with"
with $scope = new Scope() {}
// I have no idea whether the File class implements
// the required interface or not. It’s unclear what will happen in the end.
with $file = new File("...") {}
So, in Python you cannot use arbitrary objects in a with statement,
only those that implement the enter and exit contract.
Therefore, in Python there is no ambiguity in the code. The developer
understands that if with is used, it means the object definitely
implements the required interface, otherwise an error will occur. PHP
must guarantee the same behavior.
P.S.
If I’m not mistaken, a recent RFC was proposed about context managers,
which covers exactly this logic.
Ed
If PHP applies unset or enter/exit depending on whether an
interface is implemented, it will introduce hidden behavior in the
code, making it harder for developers to understand what is happening.
Compare the two cases:// I know for sure that Scope implements the interface // required to be used with "with" with $scope = new Scope() {} // I have no idea whether the File class implements // the required interface or not. It’s unclear what will happen in the end. with $file = new File("...") {}So, in Python you cannot use arbitrary objects in a with statement,
only those that implement the enter and exit contract.
Hello Ed,
Thank you for your feedback. Regarding your concern about the clarity
when using a use statement with objects that may or may not
implement a Disposable interface, it does not matter to the
application developer whether a future Disposable interface is
implemented or not.
Consider this example:
// PHP Builtin:
interface DisposableInterface {
public function dispose(?Throwable $error): void;
}
// Library Code:
interface DatabaseTransaction extends DisposableInterface {
public function execute(string $q): void;
}
interface DatabaseConnection {
public function beingTransaction(): DatabaseTransaction;
}
interface DatabasePool {
public function getConnection(): DatabaseConnection;
}
// Application Code:
function do_work(DatabasePool $pool): void {
using (
$connection = $pool->getConnection(),
) {
using ($transaction = $connection->beingTransaction()) {
$transaction->execute('...');
sleep(10); // more work.
$transaction->execute('...');
}
sleep(10); // more work
}
sleep(10); // more work
}
In this scenario, the library author might not implement Disposable
for the DatabaseConnection because its internal handle is
automatically closed on __destruct, so to them, Disposable adds no
value. However, for the DatabaseTransaction, they do implement it,
as it allows the transaction to commit or rollback based on the exit
status.
From the application developer's perspective, both are temporary
resources that are "allocated" and will be disposed of after the
scope. How they are disposed of is decided by the maintainer of that
resource (in this example, a third-party library). They might feel
__destruct is sufficient (e.g., for a socket to be closed), or they
might feel the need for Disposable to perform a specific action
based on whether the operation finished successfully.
Thanks,
Seifeddine
Hello!
function addStudentLessons(DatabaseTransaction $transaction) {
try {
$transaction->execute(...);
} catch(\Exception $e) {
Logger::log($e);
throw $e;
}
}
// Application Code:
function do_work(DatabasePool $pool): void {
using (
$connection = $pool->getConnection(),
) {
using ($transaction = $connection->beingTransaction()) {
$transaction->execute('...');
sleep(10); // more work.
$transaction->execute('...');
}
sleep(10); // more work
}
sleep(10); // more work
} // <== broken!
Logger::log($e); <== reason!
In this example, the Logger service holds the exception $e,
which completely breaks the code because the transaction will no
longer complete correctly, and it’s unclear when the resources will be
released.
This is even more true for stateful applications, where the Logger
processes stored exceptions later rather than immediately.
Note that I didn’t even use circular references. I’m sure that 90% of
PHP developers who see this code won’t even understand what the
problem is.
And in practice, it will work for about 50% of them and fail for the other 50%.
At the same time, as a programmer, I didn’t do anything particularly
wrong or make any obvious mistake in this code. It’s just that the
logging service holds the exception object for a while.
RC-managed objects were designed to create code where the destruction
time of an object cannot be determined statically (only at runtime).
Automatic memory management is not a primary feature of RC objects,
since it can be implemented without reference counting.
However, the code in the example pursues the opposite goals: it must
guarantee the exact moment a function is called. In other words, the
RAII concept is not suitable here.
And this situation is typical for PHP stateful applications, where
resource control is not managed through RAII.
Best Regards, Ed
This mimics Python's context manager protocol. The
dispose()method
would be called before__destruct, allowing objects to distinguish
between successful completion and failure.
A clarification here: this would be equivalent to IDisposable in C#, but it would not be equivalent to the Context Manager protocol in Python.
The use cases which motivated them are actually quite different: C# needed a way to handle things like pointers to unmanaged memory, so the design is closely tied to the actual resource object cleaning up its own internal state. Python was looking much more generally at common programming patterns, and a "context manager" can be separate from the resource it is managing, or even have no associated resource at all, only "enter" and "exit" behaviour.
Rowan Tommins
[IMSoP]
This mimics Python's context manager protocol. The
dispose()method
would be called before__destruct, allowing objects to distinguish
between successful completion and failure.A clarification here: this would be equivalent to IDisposable in C#, but it would not be equivalent to the Context Manager protocol in Python.
The use cases which motivated them are actually quite different: C# needed a way to handle things like pointers to unmanaged memory, so the design is closely tied to the actual resource object cleaning up its own internal state. Python was looking much more generally at common programming patterns, and a "context manager" can be separate from the resource it is managing, or even have no associated resource at all, only "enter" and "exit" behaviour.
Rowan Tommins
[IMSoP]
Hi Rowan,
My statement that it closely relates to Python, is in the sense that
if we were to add a Disposable interface, it would need to add
additional value beyond what __destruct already provides. In C#,
Disposable::Dispose ( just like in Hack ) receives no information on
whether the using scope exited successfully or due to an exception,
so having this in PHP adds no value that __destruct doesn't already
provide.
Hi Seifeddine, Tim,
I'm in favor of a feature similar to those listed in the RFC (Python’s
with [1], C#’s using [2], Hack's using [6]), but the proposal is not
equivalent to these. There are major differences that prevent it from
addressing the same use-cases.
First, the RFC proposes that variables are only unset() when leaving the
block, while Python, C#, Hack, and Java-s' try-with [3] (which is not
cited, but is similar), also immediately "close" or "dispose" of the
resource/object when leaving the block. This is important, as there are a
number of cases in which unset() alone will not immediately call a
destructor or close a resource.
Then, at least in Python, disposal is made aware of exceptions, so that it
can take different steps in that case.
The proposal relies on destructors or automatic closing of resources, but
this should not be relied on when timing matters. In general, destructors
should be avoided IMHO [4][5]. They are useful in languages with
stack-allocated variables because timing and order can be guaranteed, but
not in heap-allocated languages with automatic GC. PHP resources/objects
are heap-allocated, and its GC mechanism behavior/semantics is similar to
Java's due to cycles: resource/objects are not guaranteed to be
closed/disposed of immediately, and the order in which this happens is
undefined.
Here are some use-cases that Python's with, C#'s using, or Java's
try-with were designed to address, but are not addressed by this RFC:
// Commit the transaction as soon as the block is left, or roll it back if
an exception is thrown:
with ($db->beginTransaction() as $transaction) {
$transaction->execute(...);
$transaction->execute(...);
}
If $transaction escapes, it's not committed at the end of the block.
Regardless, it's not possible to automatically rollback the transaction in
case of exception.
// Close file descriptor as soon as the block is left:
with (get_fd() as $fd) {
// ...
}
If $fd escapes, it's not closed at the end of the block. This may affect
the program's behavior is various ways:
- The system's file descriptor limit may be reached before the GC triggers
- If $fd was a socket, and the other side waits for closing, it may hang
- If $fd has unflushed writes, readers will have an inconsistent view
// Await Scope at end of block:
with (new Async\Scope() as $scope) {
// ...
}
Again, if $scope escapes, it's not awaited at the end of the block, and
it's not possible to automatically cancel in case of exception.
Escaping/capturing is difficult to avoid, especially in large code bases,
as it can not be checked with static analysis, typing, or avoided by means
of API design. Sometimes it's even necessary, e.g. a file descriptor may be
referenced by an I/O polling mechanism.
The RFC proposes the
usekeyword. What are your thoughts on a new
usingkeyword instead, similar to C# or Hack?
A possible alternative that doesn't introduce a new keyword is Java's try
() syntax.
How do you feel about the questions raised in the "Open Issues" section?
I would prefer Option B (Restore), as this is what I would expect from
block scoping.
Introducing a Disposable interface (similar to C#'s IDisposable) to allow
objects to define custom, explicit cleanup logic that is automatically
called by use.
I'm in favor of introducing this immediately, for the reasons above, and
also because introducing this later would make it difficult to adopt the
interface (implementing IDisposable on an existing class breaks existing
code using it in use()). I have a preference for Python's interface, as it
allows to optionally decouple (and hide) the dispose logic from the
resource, makes it possible to trigger a different behavior on exception,
and also makes it easier to introduce disposables without breaking code.
Also, making the interface optional, such that use($foo) is allowed when
$foo does not implement it, may mask programming mistakes and make the
feature confusing.
[1] https://peps.python.org/pep-0343/
[2]
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using
[3]
https://docs.oracle.com/javase/8/docs/technotes/guides/language/try-with-resources.html
[4] https://externals.io/message/125696#125710
[5] https://openjdk.org/jeps/421
[6] https://docs.hhvm.com/hack/statements/using
Best Regards,
Arnaud
On Mon, Nov 3, 2025 at 10:47 PM Seifeddine Gmati azjezz@carthage.software
wrote:
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been
working on: "use construct (Block Scoping)".We wanted to raise a few initial points:
The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?How do you feel about the questions raised in the "Open Issues"
section?What are your general thoughts on the RFC?
Please find the following resources for your reference:
RFC: https://wiki.php.net/rfc/optin_block_scoping
POC:
https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scopeThanks,
Seifeddine Gmati.
Hi
Am 2025-11-04 13:31, schrieb Arnaud Le Blanc:
The proposal relies on destructors or automatic closing of resources,
but
this should not be relied on when timing matters. In general,
destructors
should be avoided IMHO [4][5]. They are useful in languages with
stack-allocated variables because timing and order can be guaranteed,
but
not in heap-allocated languages with automatic GC. PHP
resources/objects
are heap-allocated, and its GC mechanism behavior/semantics is similar
to
Java's due to cycles: resource/objects are not guaranteed to be
closed/disposed of immediately, and the order in which this happens is
undefined.
This is misrepresenting how PHP’s semantics around lifetimes work and
using that as a strawman argument to build something that does not fit
the existing semantics of PHP / the direction PHP is taking as of late.
PHP’s main mechanism of managing lifetimes is reference counting and by
that its semantics are much closer to those of languages that you call
“stack allocated”. Specifically PHP's semantics around resources and
objects match the semantics of std::shared_ptr() (C++) or Rc (Rust),
which - like PHP - are languages that guarantee that destructors are
predictably executed. Namely exactly when the reference count falls to
zero.
This is also documented and thus an explicit part of the semantics that
PHP users rely on - and not just an implementation detail:
https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor.
The file locking example using Seifeddine's PSL library from the RFC is
a real-world use case that successfully relies on these semantics.
It is true that the point in time when the reference count falls to zero
is unpredictable in case of cycles, since this is dependent on the
assistance of the cycle collector. Cycles however are a comparatively
rare situation, particularly when dealing with a resource object. These
situations are also easy to resolve using the same mechanism that one
would use in C++ to deal with shared_ptr cycles, e.g. by including a
WeakReference for one of the directions.
Here are some use-cases that Python's
with, C#'susing, or Java's
try-withwere designed to address, but are not addressed by this RFC:// Commit the transaction as soon as the block is left, or roll it back
if
an exception is thrown:
with ($db->beginTransaction() as $transaction) {
$transaction->execute(...);
$transaction->execute(...);
}If $transaction escapes, it's not committed at the end of the block.
Regardless, it's not possible to automatically rollback the transaction
in
case of exception.
This is easily solved by making the “commit” operation explicit and not
relying on exceptions for control flow. The suggested implicit commit is
dangerous, since it might accidentally commit the transaction when
undesired (e.g. when adding a guard clause with an early return). Here's
an example:
<?php
final class Transaction {
private bool $finalized = false;
public function __construct() {
echo "BEGIN", PHP_EOL;
}
public function commit() {
$this->finalized = true;
echo "COMMIT", PHP_EOL;
}
public function __destruct() {
if (!$this->finalized) {
echo "ROLLBACK", PHP_EOL;
}
}
}
use ($t = new Transaction()) {
$t->commit();
}
Nevertheless, this RFC acknowledges that use case as part of the “Future
Scope” section, as Seifeddine also mentioned in a previous reply to
Edmond: https://news-web.php.net/php.internals/129076
// Close file descriptor as soon as the block is left:
with (get_fd() as $fd) {
// ...
}If $fd escapes, it's not closed at the end of the block. This may
affect
the program's behavior is various ways:
- The system's file descriptor limit may be reached before the GC
triggers- If $fd was a socket, and the other side waits for closing, it may
hang- If $fd has unflushed writes, readers will have an inconsistent view
If $fd escapes and is nevertheless closed at the end of the block, this
may affect the program's behavior in various ways:
- Suddenly any operation on the file descriptor fails.
PHP has gradually been moving towards “making illegal states
unrepresentable”. With the migration from resources to objects and the
removal of the associated _close() functions, PHP developers and
static analysis tools can rely on the fact that having a reference to
the object means that the reference will always be valid. This is also
something that Kamil mentioned as a good thing in the RFC discussion for
the PDO::disconnect() method:
https://news-web.php.net/php.internals/128742
I'd like to note again that “The system's file descriptor limit may be
reached before the GC triggers” is misrepresenting how lifetimes work in
PHP. Unless the file descriptor somehow ends up as a part of a cycle, it
will reliably be closed exactly when nothing holds a reference to it -
i.e. when nothing is interesting in making use of the FD any longer.
Being able to let resource objects escape is a feature, since this
allows to reliably pass locks around without the resource suddenly
getting unlocked.
Escaping/capturing is difficult to avoid, especially in large code
bases,
as it can not be checked with static analysis, typing, or avoided by
means
of API design. Sometimes it's even necessary, e.g. a file descriptor
may be
referenced by an I/O polling mechanism.
This is true, but equally affects “not closing” and “forcibly closing”
the resource. In case of forcibly closing, your I/O polling mechanism
might suddenly see a dead file descriptor (or worse: a reassigned one) -
and static analysis tools need to report every single method call as
“might possibly throw an Exception”.
Introducing a Disposable interface (similar to C#'s IDisposable) to
allow
objects to define custom, explicit cleanup logic that is automatically
called by use.I'm in favor of introducing this immediately, for the reasons above,
and
[…]
I refer to Seifeddine's reply to Edmond:
https://news-web.php.net/php.internals/129076
Best regards
Tim Düsterhus
Hi,
PHP has gradually been moving towards “making illegal states
unrepresentable”. With the migration from resources to objects and the
removal of the associated_close()functions, PHP developers and
static analysis tools can rely on the fact that having a reference to
the object means that the reference will always be valid. This is also
something that Kamil mentioned as a good thing in the RFC discussion for
the PDO::disconnect() method:
https://news-web.php.net/php.internals/128742
I'm all for making illegal states unrepresentable, and I'm glad that
PHP goes in this direction.
But I don't think this is achievable or desirable for objects that
represent external resources like files or connection to servers,
which is what with() and similar mechanisms target. These resources
can become invalid or operations on them can fail for reasons that are
external to the program state. Removing close() methods will not
achieve the goal of ensuring that these resources are always valid.
If $fd escapes and is nevertheless closed at the end of the block, this
may affect the program's behavior in various ways:
- Suddenly any operation on the file descriptor fails.
This will also happen due to external factors, for example if the disk
becomes full. Having a File object that can not be closed doesn't
ensure that operations on it will not throw.
Regarding use(), there are two alternatives, with different outcomes:
- use() doesn't forcibly close resources: If a resource escapes
despite the intent of the programmer, the program may appear to work
normally for a while until the leak causes it to fail - use() forcibly closes resources: If a resource escapes despite the
intent of the programmer, the program may fail faster if it attempts
to use the resource again
The second alternative seems better to me:
- If a mistake was made, the program will stop earlier and will not
successfully interact with a resource that was supposed to be closed
(which could have unwanted results) - Troubleshooting will be easier than chasing a resource leak
Being able to let resource objects escape is a feature, since this
allows to reliably pass locks around without the resource suddenly
getting unlocked.
Would you utilize use() to lock a file in cases where the lock is
supposed to outlive the use() block?
Making objects invalid to detect bugs can also be a feature: We could
make a LockedFile object invalid once it's unlocked, therefore
preventing accidental access to the file while it's unlocked.
Escaping/capturing is difficult to avoid, especially in large code
bases,
as it can not be checked with static analysis, typing, or avoided by
means
of API design. Sometimes it's even necessary, e.g. a file descriptor
may be
referenced by an I/O polling mechanism.
This is true, but equally affects “not closing” and “forcibly closing”
the resource. In case of forcibly closing, your I/O polling mechanism
might suddenly see a dead file descriptor (or worse: a reassigned one) -
The reassigned case can not happen in PHP as we don't use raw file
descriptor numbers.
and static analysis tools need to report every single method call as
“might possibly throw an Exception”.
This is the case even if we removed every possible way to close a file
descriptor
PHP’s main mechanism of managing lifetimes is reference counting and by
that its semantics are much closer to those of languages that you call
“stack allocated”. Specifically PHP's semantics around resources and
objects match the semantics ofstd::shared_ptr()(C++) orRc(Rust),
which - like PHP - are languages that guarantee that destructors are
predictably executed. Namely exactly when the reference count falls to
zero.This is also documented and thus an explicit part of the semantics that
PHP users rely on - and not just an implementation detail:
https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor.
The file locking example using Seifeddine's PSL library from the RFC is
a real-world use case that successfully relies on these semantics.It is true that the point in time when the reference count falls to zero
is unpredictable in case of cycles, since this is dependent on the
assistance of the cycle collector. Cycles however are a comparatively
rare situation, particularly when dealing with a resource object. These
situations are also easy to resolve using the same mechanism that one
would use in C++ to deal with shared_ptr cycles, e.g. by including a
WeakReference for one of the directions.
I don't think that use() is an upgrade, if it means that I have to
think about refcounts, track reference cycles, and carefully add
WeakReferences. This seems like too low level considerations to have
when programming in a high level language.
This is not better than the problems it tries to fix.
The fact we had to introduce a cycle collector, and that most projects
don't disable it, shows that cycles exist in practice. The fact that
they exist or can be introduced is enough that thinking of PHP's GC
mechanism as something closer to a tracing GC is easier and safer, in
general. A resource doesn't have to be part of a cycle, it only needs
to be referenced by one.
I don't agree that it's easy to resolve or to avoid cycles. There are
no tools to discover or prevent them, and they don't show up in CI.
They can be introduced at any time, so a program employing use()
that works as expected today may break later due to an unrelated
change. And it doesn't always depend on the application's own code,
sometimes this happens due to a library.
But cycles are not the only issue: Variables can be captured,
accidentally or not (e.g. by a logger/tracer/cache/library/eventloop),
without the knowledge of the programmer, increasing their refcount and
extending their lifetime.
Best Regards,
Arnaud
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been working on: "use construct (Block Scoping)".
We wanted to raise a few initial points:
• The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?• How do you feel about the questions raised in the "Open Issues" section?
• What are your general thoughts on the RFC?
Please find the following resources for your reference:
• RFC: https://wiki.php.net/rfc/optin_block_scoping
• POC: https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scope
Thanks,
Seifeddine Gmati.
Hello,
One thing that isn't clear with this RFC is what happens to things redefined in the scope?
$a = 10;
use ($a = 5) {
var_dump($a);
}
var_dump($a); // unset or 10?
— Rob
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been working on: "use construct (Block Scoping)".
We wanted to raise a few initial points:
• The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?• How do you feel about the questions raised in the "Open Issues" section?
• What are your general thoughts on the RFC?
Please find the following resources for your reference:
• RFC: https://wiki.php.net/rfc/optin_block_scoping
• POC: https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scope
Thanks,
Seifeddine Gmati.
Hello,
One thing that isn't clear with this RFC is what happens to things redefined in the scope?
$a = 10;
use ($a = 5) {
var_dump($a);
}var_dump($a); // unset or 10?
— Rob
lol, I just saw "open issues" ... so, never mind. I like option B.
— Rob
Hi
Am 2025-11-03 22:46, schrieb Seifeddine Gmati:
How do you feel about the questions raised in the "Open Issues"
section?
Given the immediate and clear unanimous responses preferring option B
(restoring the original values), this is something we'll go with. I'll
look into updating the implementation later this week and we'll then
update the RFC based on the insights coming out of the implementation
(e.g. the exact semantics and possible edge cases).
With regard to the naming, which also got multiple replies in favor of
not giving use() yet another purpose, Seifeddine is currently running
analysis on a large number of composer packages to determine the impact
of various possible alternative names (let, scope, using).
Best regards
Tim Düsterhus
Hi
Am 2025-11-04 16:50, schrieb Tim Düsterhus:
Given the immediate and clear unanimous responses preferring option B
(restoring the original values), this is something we'll go with. I'll
look into updating the implementation later this week and we'll then
update the RFC based on the insights coming out of the implementation
(e.g. the exact semantics and possible edge cases).
I just updated the implementation in the branch already. The RFC text
will follow.
Best regards
Tim Düsterhus
Hi
Am 2025-11-05 17:27, schrieb Tim Düsterhus:
Given the immediate and clear unanimous responses preferring option B
(restoring the original values), this is something we'll go with. I'll
look into updating the implementation later this week and we'll then
update the RFC based on the insights coming out of the implementation
(e.g. the exact semantics and possible edge cases).I just updated the implementation in the branch already. The RFC text
will follow.
The RFC text has also been updated now to describe and showcase the
“Backup and Restore” logic that will result in the semantics expected
from full block scoping.
Best regards
Tim Düsterhus
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been working on: "use construct (Block Scoping)".
We wanted to raise a few initial points:
The RFC proposes the
usekeyword. What are your thoughts on a newusingkeyword instead, similar to C# or Hack?How do you feel about the questions raised in the "Open Issues" section?
What are your general thoughts on the RFC?
Please find the following resources for your reference:
RFC: https://wiki.php.net/rfc/optin_block_scoping
POC: https://github.com/php/php-src/compare/master...TimWolla:php-src:block-scope
Thanks,
Seifeddine Gmati.
Hello internals,
Following up on the keyword discussion: I ran an analysis tool
across 507,529 PHP files from the top +14,000 Composer packages.
Results: https://github.com/azjezz/php-syntax-analyzer#results
Summary: let, using, scope, and block have zero or minimal
conflicts (0-1 occurrences). with has 111 conflicts across multiple
packages and should be avoided if we decide to use a new keyword.
Thanks,
Seifeddine Gmati.
Hello internals,
Tim and I would like to open the discussion on our new RFC that we've been
working on: "use construct (Block Scoping)".
Hi both,
I agree with Ed and with Arnaud: this feels like it's trying to squeeze two different features into one syntax and ends up with an awkward version of both.
For what Python calls "context managers", it offers very little: the programmer is still reliant on reference counting and cycle collection to actually clean up the resource, and objects can't directly interact with the context life cycle.
Python in particular has a very carefully designed solution, and the PEP is well worth reading: https://peps.python.org/pep-0343/ I think most of that could be directly ported to PHP.
For block scoping of "normal" variables it feels clunky to add an extra block, rather than declaring the variable with a keyword like "let" or "var". This is particularly obvious in the foreach example, where the variable has to be named twice on one line:
use ($value) foreach ($array as &$value) {
Languages with a keyword for declaring variable scope instead let you write the equivalent of this:
foreach ($array as let &$value) {
I have said before that an opt-in block scope would solve my main concern about automatically capturing variables in closures, because you could write this to make scope explicit:
$foo = fn() {
let $localVar;
something($localVar, $capturedVar);
something_else();
}
With this proposal, that would again be rather verbose: a mandatory extra set of braces, to put the scope inside the closure:
$foo = fn() {
let($localVar) {
something($localVar, $capturedVar);
something_else();
}
}
I think splitting the two use cases (context managers and scoped variables) would allow us to have much better solutions for both.
Rowan Tommins
[IMSoP]