Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129582 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 2537D1A00BC for ; Wed, 10 Dec 2025 14:25:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1765376757; bh=cCIuLymqp6XJ03zD+2JXpaBtjaG4q0VDK8d3hQly+AI=; h=Date:From:To:Cc:Subject:In-Reply-To:References:From; b=JnSwoECKGsC9EllbtkGU8as651ivP2r1kGnvEpU89QOqdaJVjsDMFwajeKQt+SoiN Zg0IIMxEdVFeHR1aFDm1Zv1lEJhRSjOvoAjVnY0qQPtUMU+UcH875xBEO2agks9H2T iwfjLE5h10Kx4ci5WhrJeqS3bJHlAXEypTRD3nIUezARdoe92zBDyOV0I/fuzgdCF+ EhfytKsvmA/oreXnX3I/oR1B4IZyg3sMoX5XqrEHOD5wRetykxYGI/J8WEpaaXYSfD jxf1ld5FTedymOmrXzuVV14BR0UWnsqhxeGnLji42PLQ2qwmXL825csTqwYo6hKAj7 nPXrciPueRbYA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 72B49180077 for ; Wed, 10 Dec 2025 14:25:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from chrono.xqk7.com (chrono.xqk7.com [176.9.45.72]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 10 Dec 2025 14:25:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bastelstu.be; s=mail20171119; t=1765376748; bh=62r4dLT4c6IWezWkyfX63lVDTJyzME8msOuCxOMxVNU=; h=MIME-Version:Date:From:To:Cc:Subject:In-Reply-To:References: Message-ID:Content-Type:from:to:cc:subject:message-id; b=iNXxfAyXeplCIN9GhNomxM/eVn2lggHgzu9XL/vLcAfEFTmkmIYfXBycMyAXZ3TxW V93xoJ+KhD60gibhRWfkWZhITIn2ArB/dHL/Thp6f5RF2rKiiWQ49cWCTXKaY0YKPz +k4BYQaoSCtEYBe0HWt9k/Flledeff63y4v132Dtj5h+QhBDlfeqw7wuSkJ8jkVigl rlmDstsligzJQ+EnUYZIJoCcAdMM61QZTpevelAde3k4Ac01j4p6d3lDUrnlKNPru1 EyIk1WwHoSqfc4occmEhfVBJ1vSOyRM1kPKR2TJR2uHS0QjxwGRWXqgC4NPm5RpRbX c05uAHmd8vbfg== Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 Date: Wed, 10 Dec 2025 15:25:48 +0100 To: "Rowan Tommins [IMSoP]" Cc: internals@lists.php.net Subject: Re: [PHP-DEV] Examples comparing Block Scoped RAII and Context Managers In-Reply-To: References: <26a2f13c-f318-4d6c-9595-bfaaebcbabcb@rwec.co.uk> <78bfa50ad7c5111a1c6caaff3a525255@bastelstu.be> Message-ID: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit From: tim@bastelstu.be (=?UTF-8?Q?Tim_D=C3=BCsterhus?=) Hi Am 2025-12-10 00:19, schrieb Rowan Tommins [IMSoP]: > As promised, I eventually got back to this e-mail properly, and have > updated the examples in response. Great timing. After being on a company retreat last week and doing day job stuff earlier this week, I wanted to get back to the RFCs today and planned to send a reminder. >> The block scoping RFC has been updated to `let()`. > > Updated. You missed db-transaction/application/1b-raii-with-scope-block.php. Let me begin with some more obvious fixes I found in this new iteration: - In db-transaction/application/1c-raii-with-scope-declaration.php I am noticing that the comment mentions “extra indent” which is not a case for the 1b version either, due to the “single statement” variant being used. - file-handle/application/3a-ioc-current-closure.php is missing a closing parens and semicolon in line 20. - file-handle/application/3b-ioc-auto-capture.php is missing the same. - Both also applies to the unguarded version and the file-object version. - file-handle-unguarded/application/1b-raii-with-scope-block.php is missing a closing parens in line 11. - file-object/application/1a-raii-no-helpers.php is unsetting a `$fileWrapper` variable, this should probably be `$fh`. - file-object/application/2-context-manager.php has broken indentation. FWIW: Using VS Code to view the examples with syntax highlighting worked surprisingly well despite the new keywords. It allowed me to easily spot these typos. Less obvious issues with the locked-pdf example: - locked-pdf/application/0-linear-code.php: In this case you are closing the lock before writing into the same output file, which makes locking useless. Probably a good case in point that the “naive” implementation is insufficiently safe. The “save” arguably also belongs into the try rather than the finally, since we probably don't want to save in case of exception. - Looking further I notice that the locking issue also exists for the other implementations. - I'd argue that the ->save() belongs into the caller, rather than the RAII object or context manager, since actually saving a file is business logic that should not be hidden away. - If you would make the changes, this would effectively become equivalent to the Transaction example or the file-object example just with the extra `flock()` call and some extra business-specific logic. I think this example should be adjusted to make use of “external locking”, i.e. using a dedicated reusable single-purpose lock object that is independent of the resource in question, so that it is sufficiently dissimilar from the transaction example (though I guess it would then be equivalent to the wikimedia-at-ease example). For reference the RAII example would be just this: lock = fopen($file, 'r'); flock($this->lock, LOCK_EX); } public function __destruct() { fclose($this->lock); } } let ($lock = new Lock()) { perform_operation_under_lock(); perform_operation_under_lock($lock); // or possibly this to “prove” to the function that a lock is held. } From what I see, the locked-pdf example can be removed entirely, since it does not bring anything new to the table. Did I miss something? >> 1. >> >> For db-transaction/implementation/1-raii-object.php I'd like to note >> that it is not necessary to proxy execute() through the transaction >> object. It could also be used as a simple guard object which only >> purpose is to be constructed and destructed. > > > That's true. The "wikimedia-at-ease" example illustrates that style, > because there aren't any methods we'd want there at all. > > The drawback I see is that on a longer block, you have to come up with > a name for that unused variable, and make sure you don't accidentally > unset or overwrite it. > > > Apparently Java's workaround for that is to allow "unnamed variables", > which have the expected lifetime, but can't be accessed: > https://openjdk.org/jeps/456 Interesting, thank you for that insight. > Notably, this is *not* the same meaning for "_" as, say, C#, where "_ = > foo()" tells the compiler to discard the return value of foo(): > https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards Though I agree that such an unnamed variable is more commonly used as a discard in the programming languages I'm familiar with. For me a variable name like `$lock` or similar would be sufficiently descriptive to prevent the value from being overwritten. With the “variable backup” from the block scoping RFC even declaring the variable with a new block wouldn't cause issues, since the old value would be implicitly kept alive and restored when the inner block ends. > In the case of a transaction, it feels logical for at least the > commit() and rollback() methods to be on the Transaction class, but > other methods would be a matter of style. I've removed execute() to > take the simplest route. That makes sense to me. >> 2. >> >> The RAII object in 'file-handle' serves no purpose. PHP will already >> call `fclose()` when the resource itself goes out of scope, so this is >> existing behavior with extra steps. The same is true for the >> 0-linear-code example in file-object. You don't need the `fclose()` >> there. > > > This is true as long as nothing stores an additional reference to the > file handle. Having a separate "guard object" doesn't fully prevent > this - a reference to the guard object itself could leak - but does > make it less likely. > > It also gives somewhere to customise the open and close behaviour, such > as adding locking, or converting false-on-error to an exception (more > on that below). Yes. But since there was a specific example that didn't make use of this additional capability, I called out that specific example. > To see what it looks like, I've added a "file-handle-unguarded" > scenario, which exposes the handle much more directly: it doesn't force > fclose(), and leaves the application to handle the failure of fopen() Thank you. With regard to file-handle-unguarded/application/1b-raii-with-scope-block.php, I am not happy with either of the examples, since the style is bad in different ways. Based on the “Example showing the combination of let and if():” from the RFC and the generally accepted “happy path first” when having an if with an else block, I would personally write it like this: let ($fh = fopen('file.txt', 'w') if ($fh !== false) { try { foreach ($someThing as $value) { fwrite($fh, serialize($value)); } } catch (\Exception $e) { log('Failed processing the file in some way.'); } } else { log('Failed to open file.'); } Here the “else” block is behaving quite similarly to a “catch” block in that it does the error handling. > The CM example benefits from being able to "break" out of the using{} > block; as far as I can see, the current RFC doesn't allow that from a > let{} block, is that correct? This is correct. But I believe with my previous suggestion of writing the example being able to break out of the block is not necessary. Personally I find it pretty unintuitive that `break;` would target the `using()` block for the context manager. It feels pretty arbitrary, why is it possible to break out of `using()`, but not out of `if ()` or `try` or `catch ()`. Currently my mental model is that `break;` is used with control structures that “do multiple things” (though switch should just not have fallthrough by default and then it also would need break). If for some reason, you would like to break out of `let()`, there are some options that rely on `let()` being designed to compose well with existing functionality: Using a do-while(false) loop. This is a pattern that is somewhat known from C as a “restricted” form of goto. > The docblock in locked-pdf/application/2-context-manager.php is >> incorrectly copy and pasted. > > > Well spotted. Fixed. I'm not sure what you changed, but it's still referring to “Transaction”. I'm also now noticing that the same is true for locked-pdf/application/1a-raii-no-helpers.php. > This really demonstrates the value of the "try using() { ... }" > short-hand Larry & Arnaud have added to the CM RFC - we need the try to > be on the *outside* of the block to catch the new exception. I presume > a similar "try let() { ... }" could be added to yours? Yes, but if this is desired I would prefer not implement this as an explicit “try let” special case, but rather by allowing `try` to be followed by any statement (which includes block statements). This would then automatically compose with `let()`, just like `let()` composes with `if()` and would improve predictability of the language overall. It is not entirely trivial to implement, since the “dangling else” ambiguity (https://en.wikipedia.org/wiki/Dangling_else) would then exist as a “dangling catch” ambiguity, but it should be possible. Best regards Tim Düsterhus