Hi, On Wed, Nov 5, 2025 at 1:39 PM Tim Düsterhus <[email protected]> wrote: > 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: 1. 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 2. 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 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. 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
