Re: [RFC][Discussion] use construct (Block Scoping)

From: Date: Thu, 06 Nov 2025 02:40:23 +0000
Subject: Re: [RFC][Discussion] use construct (Block Scoping)
References: 1 2 3 4  Groups: php.internals 
Request: Send a blank email to internals+get-129105@lists.php.net to get a copy of this message
On Wed, 5 Nov 2025 at 06:27, Edmond Dantes <edmond.ht@gmail.com> wrote:
>
> 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:
>
> ```php
> // 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
// 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


Thread (42 messages)

« previous php.internals (#129105) next »