Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129790 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 87C951A00BC for ; Mon, 19 Jan 2026 16:00:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1768838416; bh=e0ntytwY45DaQ2uJmbLSaGsERqteas1WrI23obD3nkc=; h=Date:From:To:In-Reply-To:References:Subject:From; b=T60rTHFKbXulv+EIJAzNoENEA92JaOQXlHdIpcw7GAZFfZ+315mquJkxmuRI3c2J8 uYwD4UAbqhUorz2QblxmioL+jz1b8pDB/CnAioRLjNTBX3VxrIP0ifEEHiDd9toqDl UTplaBV9Hvvo06OlTlAB2EfeiKg1korgq31sTXPsHg/law7voPNzk+2e+/YPY6hfil sfD1+S5bQgHgSoitBDzz9vSipDj+8JTBSBaB965UPEHAJL2d93q29Z3Peq99qFOwm4 7akp0jo32AP7WrhddVv3nKmHZRIW4yLK9zLU8op0COAgb6i/Kq5BSBCP9e4tu7dUYk odl0bT803M9zQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 23214180072 for ; Mon, 19 Jan 2026 16:00:15 +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.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS,SPF_NONE autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-a6-smtp.messagingengine.com (fhigh-a6-smtp.messagingengine.com [103.168.172.157]) (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 ; Mon, 19 Jan 2026 16:00:14 +0000 (UTC) Received: from phl-compute-04.internal (phl-compute-04.internal [10.202.2.44]) by mailfhigh.phl.internal (Postfix) with ESMTP id 7294C140078A for ; Mon, 19 Jan 2026 11:00:09 -0500 (EST) Received: from phl-imap-02 ([10.202.2.81]) by phl-compute-04.internal (MEProxy); Mon, 19 Jan 2026 11:00:09 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to; s=fm2; t=1768838409; x=1768924809; bh=WoZaBEn567GbQ7foYq7R7 h/TYLfdJ4CxO5/qvebMSds=; b=ULmyP3f/dCqLQ0M20zFTs6Gi3zAr4oT3sOLD2 7MU8aUqkjrEnCOD/MmOAy4LgIutk9EVRjBtqi/WZoMysrIxpbUL8ffZWTT9VnDY/ LBmPvSSv0erCOjgS6y2vDMVW1i6HUU++yzYh0GSW3xIuMOTnUnuTfNkDftqYNv90 Lv8dTQjPqyYK85dyIHSZup9uoc40hqH4U96tHn4FzWftwbzJx4dXOR4iLORY7ScM 5rIemzLh5noj/uuedi/ZcLQADc8xGo1Gn16Mwk4DThbvXq70yeEbuHHHoCS4lOvc bAYEwmmSgz+A3mM2DwGS01waUGAgdA7MgtgOuHPMMG1jS42jA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm2; t=1768838409; x=1768924809; bh=W oZaBEn567GbQ7foYq7R7h/TYLfdJ4CxO5/qvebMSds=; b=fSD/x7ArsTdaH/Ry1 gyV1ZerXADwSED5rpOLTaSlMhLwMmGH1FeKa1LmVln2M7Okbzskfaq/XlhHSuEvC dr6eve48p06LaL/5/UTodQdC8+jaJzWxX8R7Y4XmEEFHQtba/zVsW8jCnSCLlIlj DURjTtOZ0bBIIH08l2/hnyAVOpU4NrnwB6oEuRTdC0cgpRxoI/ZJu/dJsAT5FvmV 7X0uDiTiRqq5T0ry7f+/i83QcOChBLUTpgQteCXfec6jw/bEm5pe3cE4g9SAmsb7 6uG7LTLYMnSXVya3k0BA5r8sTrvHeKZ5KQ3BpmKKvGNWrDG94oNOfIiTlK2ELl0v MCTVw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddufeejleekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepofggfffhvffkjghfufgtgfesthhqredtredtjeenucfhrhhomhepfdfnrghrrhih ucfirghrfhhivghlugdfuceolhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhmqe enucggtffrrghtthgvrhhnpeefuddtjeffvdettdevveehveelteevtdfhtefhgefgleei ffekgeelffelieehudenucffohhmrghinhepuhhsvghfuhhlrdgtohguvghsnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgrrhhrhiesghgr rhhfihgvlhguthgvtghhrdgtohhmpdhnsggprhgtphhtthhopedupdhmohguvgepshhmth hpohhuthdprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 3206E700065; Mon, 19 Jan 2026 11:00:09 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: ALvh3TDXSVwZ Date: Mon, 19 Jan 2026 09:58:17 -0600 To: "php internals" Message-ID: In-Reply-To: References: Subject: Re: [PHP-DEV] [RFC] Context Managers Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable From: larry@garfieldtech.com ("Larry Garfield") On Thu, Jan 15, 2026, at 12:38 PM, Volker Dusch wrote: > Hi Larry, Hi Internals, > > Last year I promised you (Larry) some feedback on-list as well and > didn't get around to it until now. I recognize the strain that > repeating arguments has on a discussion like this, and this topic has > already taken up a lot of focus and time for the folks here. > > But I wanted to at least explain why I think PHP would be better off > without having this in core and why I think it would be a net negative > for PHP to have this. > > So to summarize, I find the feature doesn't fit in PHP. It's > introducing more magically called methods, burdened with unnecessary > complexity, while being very limited in its potential (sensible) uses. > Combined with its class-only high-verbosity approach, I feel this is > lacking places where it would improve PHP code in general and better > suited for a library for people that want this type of, what I > consider, magical indirection. As noted in Future Scope, we can add function-based context managers as = well based on generators. At the moment we're not convinced it's necess= ary, but it's a straightforward add-on if we find that always writing a = class for a context manager is too cumbersome. The issue with punting this behavior to user-space is that a library can= not provide this sort of functionality in a clean way. In an ideal world, if we had auto-capturing long-closures, then I would = agree this is largely unnecessary and could instead be implemented like = so (to reuse the examples from the RFC): $conn->inTransaction(function () { // SQL stuff. }); $locker->lock('file.txt', function () { // File stuff. }); $scope->inScope(function () { $scope->spawn(yadda yadda); }); $errorHandlerScope->run(fn() =3D> null, function () { // Do stuff here with no error handling. }); And so forth. If we had auto-capturing closures, I would probably argue= that is a better approach. However, auto-capturing closures have been rejected several times, and I= have no confidence that we will ever get them. (Whether you approve or= disapprove of that is your personal opinion.) The current alternative = involves using lots of `use` clauses, which is needlessly clunky to the = point that folks try to avoid it. I literally have code like this in a project right now, and I've had to = do this many times: public function parseFolder(PhysicalPath $physicalPath, LogicalPath $log= icalPath, array $mounts): bool { return $this->cache->inTransaction(function() use ($physicalPath, $l= ogicalPath, $mounts) { // Lots of SQL updates here. }); } That's just gross. :-) This is exactly the example that's been used in = the past to argue in favor of auto-capturing closures, but it's never be= en successful. So given the choices we have made to limit the language, context manager= s become the next logical option to encapsulate common error handling pa= tterns. We chose to pursue this syntax now because of the ongoing async discussi= ons, as IMO, full structured-only concurrency is the Right Way forward. = So rather than a one-off for async, it's better to have a generic synta= x that would work for a dozen use cases, not just one. As to it being too "magical," the definition of that is, as always, high= ly subjective. Magic is just code I don't understand. It should be not= ed that what is proposed here is almost identical in design to Python, w= hich makes heavy use of this design and is widely regarded as one of the= easiest languages to learn. So it's clearly not too magical for beginn= ers. > With the name also being extremely generic and non-descriptive, this > all feels like bloat to me that complicates the language for no > tangible benefits. The name was borrowed from Python, which should make it easily understan= dable for all of those beginners who started on Python. If there's a be= tter name for the Interface you'd like to use, though, we're open to sug= gestions. Similar (if less robust) functionality exists in C#, also cal= led Context Managers (https://useful.codes/working-with-context-managers= -in-c-sharp/). Context Managers are also used in Java, again for simila= r but not as robust functionality (https://useful.codes/working-with-con= text-managers-in-java/). We modeled on Python, as it was the most robust of the existing options,= but "context manager" does seem to be the de facto standard name for th= is pattern. > To expand a bit on the points: > > - Non-local behavior: > > Every using statement is a couple of function calls that are > non-obvious in how they delegate to some __magic interface methods > that are not supposed to (but very able to) be called explicitly. With > the implicit catch and exitContext() ability to return true/false; to > rethrow/suppress an exception, adding even more hidden branching to > execution. __construct, __destruct, __get, property hooks, ArrayAccess, Iterable, _= _serialize, ... PHP decided that triggering "hidden" behavior at certai= n points was acceptable decades ago. If anything, the use of a dedicate= d keyword here makes it less magical than __get or property hooks, as it= clues the reader in that a context manager is being used. And in every= one of those cases, it's technically possible to call the magic or inte= rface method explicitly, but it's culturally discouraged. There's no re= ason Context Managers should not be in the exact same category. If we could use auto-capturing closures, it would effectively just be th= e strategy pattern. But as above, PHP has decided that we need a few mo= re steps to make that work, which Context Managers resolve. > - Variable masking: > > A new block masking and restoring the context variables but not others > is an additional source of errors and confusion that I feel doesn't > pay off in terms of value vs. added complexity and error sources. > > It's not behavior we have anywhere else in PHP and breaks the flow of > reading and reasoning about code in non-obvious ways. This was added largely because it was requested for the block scoping RF= C, and it seemed to make sense here too. If the consensus is that it's = not worth it, we're OK with pulling that part back out. It's not core f= unctionality. Does anyone else feel strongly either way on this point? > - Break/Continue semantics: > > There is no clear reason for me why this block scope should allow > early returns. If the content is growing to a point where it's needed, > a function is already a reasonable scope. Given that PHP allows for > `break 2;`, something that we'll see more of then, it's manageable. It > just adds to the, for me, unreasonable complexity of the feature. Even a 4 line block could have an if statement in it, which may involve = terminating the block early without throwing an exception. Without `bre= ak` or similar, the only option for the user would be a `goto` and a lab= el after the `using` block ends. I hope we don't need to explain why th= at is an inferior solution. As far as an example (to Tim's email): function ensure_header(string $filename, array $header) { using (open_file($filename) as $f) { $first_line =3D fgets($f); $first =3D parse_csv($first_line); if ($first[0] =3D=3D=3D $header[0]) { break; // The thing we want to ensure is already the case, = so bail out. } // Logic here to prepend $header to the file. } } While it would be possible to reverse the conditional, almost every mode= rn recommendation is to use early returns as much as possible. Or in th= is case early `break`. =20 > - Naming: > > For me, despite having worked with Python, the name means absolutely > nothing. It doesn't even manage the context of the invocation. If > anything, it manages when a resource is released into and removed and > deallocated from a scoped context. And that sentence also is rough. > > PHP, for better or worse, doesn't burden its users with having to > study many CS concepts beyond basic OO or procedural programming and > still allows them to write obvious, valuable, and predictable code. I > understand that with its evolution this has changed, and we have added > a lot of redundancy (short arrays, short functions, pipes, etc..) to > provide sugar that has steepened the learning curve for some. > Adding very specific single-use concepts to the language with their > own disconnected naming schemes, syntax, or, in this case, hidden > behaviors should be carefully considered. And while I'm sure you did > we came to different conclusions. Again, I go back to the example of Python, often lauded as a great begin= ner language. It lets you write procedural or OOP (though it does have = multiple inheritance), though it's weaker on functional than PHP 8.6 wil= l be, I'd argue. But it makes heavy use of context managers, and it doe= sn't seem to hurt anyone. As far as redundancy, that's in large part because PHP was never designe= d, it's just been patched over the last 30 years. But often, we're just= moving up the abstraction curve along with the rest of the language com= munity. Or identifying common patterns and problems and finding ways to= extract out the hard bits to make them easier. CSS, incidentally, evol= ves the same way: Find common patterns and problems, figure out a genera= l language-level solution, and add new features to the language to turn = "500 lines of Javascript" into "2 CSS keywords." Remember, all code is syntactic sugar over assembly. :-) > - Block scoping: > > Personally, I don't see the need for block scoping in PHP in general. > But having a generic solution that works without creating a new class > for each case would feel like something that at least can be used by > everyone and every part of the language. I disagree that "everyone" will have to write a CM. In practice, I'd ex= pect most people wouldn't; it would be part of the API exposed by librar= y X, and users of that library will use the CMs that are provided. The = whole point is that the logic is reusable, and thus reduces the need for= "everyone" to write it. For example, Doctrine could provide a single I= nTransaction CM, which every single user of Doctrine would benefit from.= (Much the same as Doctrine's existing inTransaction() method, which su= ffers from the use-bloat problem described above.) PHP itself could pro= vide a single CM for files, possibly using SplFile, so no one else would= have to write one, ever, unless they needed some highly wonky custom lo= gic. In which case they'd be custom writing something anyway. But this= way, they get the recommended error handling out-of-the-box in the stan= dard case. As far as a "generic solution," I have added a section to the RFC on "va= lue escape," based on an observation I made a while back in the bonus th= read. Specifically, there will *always* be a failure case if the contex= t variable escapes (or its equivalent in traditional code), but there is= no universal answer to what failure case you want. A Context Manager a= pproach allows you to explicitly decide that for each situation as neede= d. > Tying this to custom objects doesn't feel like a language level > feature but something that should be in a library. As noted above, PHP has deliberately chosen to make library-based soluti= ons to this space inferior. > The worst option would be to allow using() to take a context manager > or a plain expression and make people guess every time the statement > is used if hidden function calls are attached to it. So we'll mark you as a no on having that fallback shorthand, then. :-) = Would you rather a rudimentary `UnsetAtEnd` CM be included? > - Verbosity: > > Having to implement three code paths for each ContextManager (enter, > exitWithoutError, exitWithError) within two functions, with a near > mandatory `if` in a separate class, doesn't strike me as useful over > patterns like getting and returning a connection out of a pool > =E2=80=9Cmanually.=E2=80=9D The trade-off between this and already exi= sting solutions > to this problem with try/finally or construct/destruct isn't enticing. I disagree, naturally. Just from the examples in the RFC, I'd say the r= esulting code is far cleaner, less redundant, easier to read, and you're= less likely to forget error handling. We debated a 2 method vs 3 method solution, that is, splitting exitConte= xt() into exitSuccess() and exitFailure(). The challenge there is that = if you have common logic to happen in both cases, you have to duplicate = it. Merging them into exitContext(), you have to deal with an if-statem= ent most of the time. Either way is a trade off. Additionally, you may= not want anything to happen on one of exitSuccess() or exitFailure(), i= n which case you'd have an empty method, or else we use magic methods in= stead of an interface, which we weren't wild about. So no approach is perfect, so we started with the one that Python has al= ready shown is useful and effective. If there's a different way to orga= nize that code that you think would be better, we're open to suggestions. > - Object lifecycle in PHP: > > Just to reiterate because it bugs me as PHP zval life cycles are used > as an argument here: Reference counting in PHP is fully deterministic, > and code like `function () { $x =3D new Foo(); return; }` will > deterministically construct and destruct (at the end of the function > as the variable gets cleaned up). Use cases where the GC would > actually come into play are extremely rare from the real-world usages > we can see in Python. I also haven't seen an example in PHP nor > something in the RFC that looks overly convincing in improving this > with managed in-function unsets. The error handling option is nice, > but for maintainability, simplicity, and effort in writing code, I'd > still prefer this to try/(catch)/finally To reiterate what I said above and in the new section in the RFC: The is= sue isn't about reference counting determinism at the engine level. The= issue is developer A may expect something to happen when an object goes= out of scope, but it won't because developer B stashed a copy of it som= ewhere so it won't actually destruct. That problem is not created by context managers, and it affects the Bloc= k Scoping proposal as well. It's an unavoidable fact of basically any l= anguage with automatic garbage collection. You can predict when a varia= ble goes out of scope, but you cannot prevent a reference to its value f= rom continuing to exist past when you expect it, thus delaying any on-cl= eanup behavior beyond when you expect it. What context managers offer is a way to decide what to do with that situ= ation, because, again, there is no globally applicable answer. But this is one reason that relying on destructors is a poor approach if= you want cleanup X to happen at point Y: You can't be certain the destr= uctor will be called then, even if the reference counting logic is fully= deterministic. On top of that, destructors, as noted, cannot different= iate between success and failure cases, which often require different cl= eanup. Externalizing that logic out of the value itself (from the conte= xt variable to the context manager) allows flexibility in both cases tha= t simply does not exist otherwise without a large amount of code. Python recommends using CMs for files and similar values precisely for t= his reason, and has essentially the same ref-count-plus-cycle memory mod= el. > Layering another level of lifecycle management on top of the existing > PHP behavior doesn't feel like a simplification but rather like > another source of complexity with this new niece special case. > > -- > > In summary, this feels like beyond what's necessary to get rid of a > couple of try/finally blocks per application and encourages bad > patterns like using ContextMangers for async instead of more modern > APIs that have evolved since then. I would argue that context managers for async *is* the more modern API, = and creating/canceling/blocking async tasks manually is the legacy, poor= approach. --Larry Garfield