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#'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.

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

Reply via email to